Этот курс ( Практикум по разработке CMS ) создавался, когда у PHP была версия 5.3 или 5.4. Со временем какие-то функции PHP устаревают (mysql, each), какие-то начинают работать по-другому (empty). Пожалуйста, следите за изменениями в PHP по сайту php.net! |
Серверные технологии
Доступ к объектам редактирования
Ещё раз напомним, что сайт состоит из элементов HTML, в которых мы показываем пользователю данные, извлечённые из БД на сервере. Данные хранятся в ячейках mysql-таблиц. Для доступа к данным со страницы HTML надо послать на сервер фоновый HTTP-запрос, содержащий наименование mysql-таблицы, номер строки (id) и наименование поля (не всегда: если наименования поля нет, форма будет создаваться для всех полей, хранимых в данной строке таблицы).
Везде, где на html-странице выводятся mysql-данные, мы создаём ссылки для редакторского доступа к этим данным. То же и для фото: к каждой миниатюре в списке изображений на станице мы прикрепляем ссылку для редактирования соответствующей строки в таблице Файлы. Ссылки всегда создаются только на стороне клиента (Javascript); они, разумеется, создаются только в том случае, если от Php поступает соответствующий сигнал – в конце страницы написано Editor.editable = true;
Но что если у вас на сайте в шапке размещён "слайдер", картинки в котором мелькают так быстро, что не успеешь кликнуть по ссылке редактирования? И что если вы захотели на время скрыть страницу (но не удалять насовсем)? Как получить доступ к таким неотображаемым объектам? Надо создавать специальные списки для всех mysql-таблиц. Чтобы не загромождать текущую страницу такими списками (или даже просто ссылками на них), поместим все эти "общие" ссылки на страницу входа в Редактор – edit.com. На этой странице ведь сейчас ничего нет, кроме формы авторизации (либо надписи "Пользователь: admin").
Как, кстати, теперь можно попасть на страницу авторизации? Раньше, в версии 06 (http://hichtig.ru/06/) мы делали для режима DEBUG верхнее меню, где, в частности, была ссылка на страницу редактирования. Теперь мы от такого меню отказались. Поэтому надо либо набирать адрес edit.com руками, либо можно нажать сочетание клавиш Ctrl + Alt + e.
Так вот, на странице edit.com мы создали список ссылок (сейчас их три: Страницы, Файлы, Сообщения) для доступа к любой строке любой таблицы Mysql. На самом деле это не ссылки, а эмуляция "кнопок" – элементы SPAN, при щелчке по которым создаётся "выпадающий" список и элемент input, при вводе в который букв или цифр выпадающий список фильтруется (запросами вида select * from file where title like '%море%', которые формируются на сервере при обращении к URL вида js_list.com?table=file&id=3).
При щелчке по элементу списка значение вставляется в поле input и генерируется форма редактирования найденной строки (в текущей таблице mysql). Можно не щёлкать по списку, если знаете точно номер строки – просто ввести этот номер в поле и нажать красную стрелку справа от поля, тогда тоже сгенерируется форма с данными, соответствующими введённому номеру (id).
Вы помните, что все javascript-обработчики у нас начинают работу при получении специального сигнала от Php. По какому сигналу javascript в браузере начинает генерировать кнопки редактирования элементов? (это вопрос для домашнего задания)
HTTP-протокол, заголовки сервера
В версии 05 (http://hichtig.ru/05/) мы добавили нашей CMSphp-функцию _headers(), хотя до этого всё вроде бы прекрасно "работало само": веб-сервер Apache отправлял нужные заголовки ответа. Правда, мы тоже отправляли "свои" заголовки – в двух случаях:
- когда надо было сообщить, что страница не найдена (код ответа 404);
- когда надо было перенаправить пользователя на другую страницу (заголовок "Location: " с невидимым кодом ответа 302 – "временно перемещён").
Необходимость отправлять "свои" заголовки возникла прежде всего в связи с фоновыми запросами к серверу (вида js_list.com?table=file&id=3), которые должны возвращать вовсе не html, а javascript. А веб-сервер по умолчанию все ответы, организованные с участием Php, сопровождает заголовком "Content-type: text/html...". Поэтому php-функция js_list обязана отправить заголовок Content-type: text/javascript... (у нас это делает не она сама, а дополнительная функция js_send). Важно не забывать и о кодировке; полностью этот заголовок должен выглядеть так:
Content-Type: text/javascript; charset=UTF-8
Сейчас по умолчанию на всех линукс-серверах и так используется кодировка UTF-8. Но вдруг ваш веб-сервер работает под Windows? Поэтому заголовок Content-type мы отправляем из php всегда, для любого типа содержимого (и javascript, и html).
Обычная вежливость также заставляет нас отправлять заголовок 304 Not Modified, если страница с прошлого раза не изменилась – чтобы браузер не скачивал её заново, а мог брать из своего кэша; и чтобы наш веб-сервер лишний раз не напрягался, генерируя страницу заново.
Насчёт "прошлого раза" здесь должен позаботиться сам браузер (или другой клиент) – если он раньше уже открывал эту страницу, он отправит нам заголовок If_Modified_Since с датой создания страницы. А для этого, в свою очередь, мы должны отправлять браузеру даты создания страниц – в заголовках вида Last-Modified: ....
Мы так и делаем: дата создания страницы хранится у нас в поле ctime ("creation time"). Сейчас эту дату можно считать верной лишь приближённо, так как на странице, например, мог появиться более поздний комментарий (и при этом содержимое страницы изменится – кэш браузера устареет). Это значит, нужно добавить специальную функцию, которая при изменении каждой сущности, связанной с данной страницей (Комментарии, Файлы), будет обновлять время создания страницы – поле ctime. Сделаем эту функцию позже (или вы сами попробуйте сделать в качестве домашнего задания).
ЧПУ и "роутер"
ЧПУ расшифровывается как "человекопонятный УРЛ", где аббревиатура УРЛ (Universal Resource Locator) используется в значении "адрес страницы". Внешнее, формальное отличие заключается в том, что в ЧПУ мы не используем параметры запроса (вида ?id=1), а пишем в адресе просто "1": localhost/1. Или "localhost/1.html", с расширением.
Можно однако применить и более содержательные адреса – писать их транслитом или использовать перевод на английский: "kontakty.html" или "contacts.html". Мы будем использовать транслит, потому что он требует меньше ресурсов при автоматической генерации (и человеческих, и машинных). Но редактор всегда может своей рукой написать вместо транслита английские слова (или латинские). Все пробелы будут заменены на подчёркивания.
У вас должен возникнуть вопрос: как запрос адреса contacts.html заставит работать программу, вызываемую из файла index.php? Если сказать коротко, это делается с помощью перенаправления запросов в файле .htaccess. Мы не будем здесь рассматривать синтаксис mod_rewrite сервера Apache, в нашем учебном сайте файл .htaccess совсем небольшой, с достаточно очевидными настройками. Мы описывали выполняемые им задачи в Главе 19, приведём здесь его код полностью:
AddDefaultCharset UTF-8 RewriteEngine On RewriteCond %{HTTP_HOST} ^www\.nichtig\.ru$ RewriteRule ^(.*)$ http://nichtig.ru/$1 [L,R=301] RewriteRule main-1.html$ http://nichtig.ru/ [L,R=301] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-l RewriteCond %{REQUEST_FILENAME} !-d RewriteRule .* index.php [L] Options -Indexes
Для использования длинных адресов нам понадобится ещё одно поле в таблице page – назовём его url. Заполнять его – не такая тривиальная задача. После сохранения страницы надо будет вызывать функцию set_page_url, проверять в этой функции, что написал редактор в поле url (и если отправил пустое поле, заполнять его транслитом из поля title), затем изменять запись в таблице page.
Заполнять поле url приходится именно после сохранения страницы, потому что в него мы дописываем в конец значение поля id (через дефис): contacts-4.html. Это решает несколько задач:
- уникальность адресов в таблице (можно создавать несколько страниц "Обо мне" – к каждой в конце будет дописываться уникальный id);
- на стороне клиента, в javascript можно получать идентификаторы страниц;
- мы сохраняем в качестве основного ключа id и внутри php будем использовать именно его – короче и удобнее.
Теперь, когда мы "договорились" о системе адресов, можно организовать их распознание на сервере и выдачу соответствующих данных. Создадим для этого целый отдельный класс – Route. В нём одна основная, "рабочая" функция response() и три вспомогательных.
- Смотрим на адрес: contacts-3.html или write_wr.com; определяем сущность по расширению – связь сущностей с расширениями в массиве Config::$private['subjects'].
- Если сущность command, вызываем через основную функцию класса Command command_wr ту функцию, которая указана в имени (без расширения) – write_wr, get_data_js, load_sql... Разумеется, мы вызываем только те функции, которые тут же и перечислены (в функции Route::response), а для неизвестных даём ответ 404.
- Если сущность page, ищем запись в таблице сложным способом: если имя оканчивается на дефис-число, ищем по id, если числа в конце имени нет, ищем по полю url (это поле также является уникальным индексом) – вдруг кто-то не захотел, чтобы в конце имени были цифры.
Например, я не захотел – при создании новой страницы: там создаётся имя "new_page", ему уникальные цифры в конце не нужны принципиально, потому что в системе в данный момент времени может быть только одна "новая страница".
Или, например, мы устанавливаем нашу новую CMS на сайт со старыми данными, а там уже есть все готовые адреса страниц, и их менять нельзя - тогда там тоже не будет чисел в конце адресов.
- Если сущность не найдена, даём ответ 404.
- Если страница по идентификатору не найдена, даём ответ 404.
- Если страница найдена, залезаем в класс Page и заполняем полученными из БД данными массив Page::$data['row'].
- Вызываем функцию Page::$tpl(), где значение $tpl берём из соответствующего поля в полученных данных ("tpl").
- Запрещаем в адресе параметры (строку после знака "?") для сущности page, для command разрешаем – для вызова пользовательских функций класса Ab с параметрами. Пользовательских – в том смысле, что какой-то другой программист, не автор нашей CMS, позже может написать какую-то свою функцию для обработки данных на сайте, и ему не надо будет её "регистрировать", изменяя код нашей базовой функции Route::response. Эту новую функцию надо будет писать в файле Abadon.php, в классе Ab.