Опубликован: 22.10.2016 | Уровень: для всех | Доступ: платный
Лекция 4:

Серверные технологии

< Лекция 3 || Лекция 4: 1234

Доступ к объектам редактирования

Ещё раз напомним, что сайт состоит из элементов 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 в браузере начинает генерировать кнопки редактирования элементов? (это вопрос для домашнего задания)


Рис. 4.7.

Рис. 4.8.

Рис. 4.9.

Рис. 4.10.

Рис. 4.11.

Рис. 4.12.

HTTP-протокол, заголовки сервера

В версии 05 (http://hichtig.ru/05/) мы добавили нашей CMSphp-функцию _headers(), хотя до этого всё вроде бы прекрасно "работало само": веб-сервер Apache отправлял нужные заголовки ответа. Правда, мы тоже отправляли "свои" заголовки – в двух случаях:

  1. когда надо было сообщить, что страница не найдена (код ответа 404);
  2. когда надо было перенаправить пользователя на другую страницу (заголовок "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. Это решает несколько задач:

  1. уникальность адресов в таблице (можно создавать несколько страниц "Обо мне" – к каждой в конце будет дописываться уникальный id);
  2. на стороне клиента, в javascript можно получать идентификаторы страниц;
  3. мы сохраняем в качестве основного ключа id и внутри php будем использовать именно его – короче и удобнее.

Теперь, когда мы "договорились" о системе адресов, можно организовать их распознание на сервере и выдачу соответствующих данных. Создадим для этого целый отдельный классRoute. В нём одна основная, "рабочая" функция response() и три вспомогательных.

  1. Смотрим на адрес: contacts-3.html или write_wr.com; определяем сущность по расширению – связь сущностей с расширениями в массиве Config::$private['subjects'].
  2. Если сущность command, вызываем через основную функцию класса Command command_wr ту функцию, которая указана в имени (без расширения) – write_wr, get_data_js, load_sql... Разумеется, мы вызываем только те функции, которые тут же и перечислены (в функции Route::response), а для неизвестных даём ответ 404.
  3. Если сущность page, ищем запись в таблице сложным способом: если имя оканчивается на дефис-число, ищем по id, если числа в конце имени нет, ищем по полю url (это поле также является уникальным индексом) – вдруг кто-то не захотел, чтобы в конце имени были цифры.

Например, я не захотел – при создании новой страницы: там создаётся имя "new_page", ему уникальные цифры в конце не нужны принципиально, потому что в системе в данный момент времени может быть только одна "новая страница".

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

  1. Если сущность не найдена, даём ответ 404.
  2. Если страница по идентификатору не найдена, даём ответ 404.
  3. Если страница найдена, залезаем в класс Page и заполняем полученными из БД данными массив Page::$data['row'].
  4. Вызываем функцию Page::$tpl(), где значение $tpl берём из соответствующего поля в полученных данных ("tpl").
  5. Запрещаем в адресе параметры (строку после знака "?") для сущности page, для command разрешаем – для вызова пользовательских функций класса Ab с параметрами. Пользовательских – в том смысле, что какой-то другой программист, не автор нашей CMS, позже может написать какую-то свою функцию для обработки данных на сайте, и ему не надо будет её "регистрировать", изменяя код нашей базовой функции Route::response. Эту новую функцию надо будет писать в файле Abadon.php, в классе Ab.

Рис. 4.13.

Рис. 4.14.

Рис. 4.15.

Рис. 4.16.

Рис. 4.17.

Рис. 4.18.
< Лекция 3 || Лекция 4: 1234
Михаил Гутентог
Михаил Гутентог

Этот курс ( Практикум по разработке CMS ) создавался, когда у PHP была версия 5.3 или 5.4. Со временем какие-то функции PHP устаревают (mysql, each), какие-то начинают работать по-другому (empty). Пожалуйста, следите за изменениями в PHP по сайту php.net!

Александр Мельников
Александр Мельников

Изучаю курс "Практикум по созданию CMS" в листинге 4.3

$n = count($_GET); if ($n > 0) { $param = each($_GET); // самое простое: пропускаем только первый параметр if ($n > 1 || !isset($valid[$param['key']])) { _404(); }

При попытке просмотра в браузере получаю ошибку: Deprecated: The each() function is deprecated.  И не пойму как исправить ситуацию.

Елена Суханова
Елена Суханова
Россия, Москва, МИЭТ, 2011
Анастасия Щитова
Анастасия Щитова
Россия, Москва, ФГБОУ ВО "Московский государственный юридический университет имени О.Е. Кутафина (МГЮА)", 2016