Предыдущие части: Финал CRUD и SQL-инъекции, Заполнение формы, Действие Update, CRUD, Форма редактирования, PHP и база данных, Идентификаторы, Работаем с таблицами, Создаём базу данных, Установка MySQL
Читайте также: Как работает веб-сайт, Бэкэнд на PHP, Введение в язык PHP
Код для данного выпуска находится на github в ветке html
Мы закончили полный цикл создания функционала сайта: сайт умеет показывать данные, принимать их и редактировать. Сейчас данные это заметки (таблица базы note). Если вы захотите сделать что-то другое, например, рецепты, то вам нужно всего лишь повторить пройденное: спроектировать и сделать в базе таблицу recipe, сделать контроллер recipe.php, сделать для него представления update, view и т.д.
Но пока что есть проблема, из-за которой пользоваться сайтом нельзя. Покажу на примере. Создадим новую заметку:
Вроде ничего необычного, но "My Title" я написал в кавычках. Конечно, мы можем использовать кавычки, когда пишем какие-то тексты, не так ли?
Сохраняем эту запись:
Всё в порядке. Заголовок сохранился вместе с кавычками. Теперь попробуем отредактировать эту запись:
И мы видим, что заголовок исчез. Что случилось? Посмотрим на исходный код страницы, который прислал нам сервер. Для этого нажмём Ctrl-U:
И мы видим, что значение "My Title" вставилось в атрибут value тэга <input>. Но так как значения атрибутов записываются в кавычках, а наш текст тоже содержит кавычки, то получились две кавычки подряд. Иначе говоря, пустая строка. А всё, что идёт дальше – стало просто мусором внутри тэга, который не отображается.
Вот так мы сломали HTML и потеряли зачение поля <input>.
Эта ситуация полностью аналогична SQL-инъекции, и выход из неё такой же: нужно экранировать кавычки.
Но мы не можем экранировать кавычки с помощью "\", как в языках программирования. Мы сейчас в царстве HTML, где действуют другие правила. Для служебных символов в HTML есть специальные коды. Например, кавычки это вот такой код:
"
И значит, чтобы корректно вставить нашу строку в атрибут тэга <input>, надо написать вот так:
value=""My Title""
Напишем в контроллере note.php функцию html_convert() для преобразования нашей модели в HTML-пригодный вид. Мы будем передавать в неё модель, а функция будет брать все поля модели и конвертировать их. После чего вернёт новую модель с новыми полями (чтобы не трогать оригинальную), так как она нужна только для вывода в HTML.
И также, когда отдаём модель в рендер, обернём её в вызов функции:
render('main', 'form', html_convert($model));
Обновим страницу редактирования заметки:
Теперь всё в порядке. Посмотрим на HTML-код:
Кавычки заменились на "
Но кавычки это ещё не всё. Как можно видеть, знак "&" в HTML служебный, так как именно он отвечает за коды спецсимволов, а значит, использовать "&" в тексте мы тоже не можем. Нужно заменять его кодом &
Знаки "<" и ">" отвечают за начало и конец тэгов, поэтому их тоже нельзя использовать, а нужно заменять на < и >
Вы можете посмотреть, какие ещё бывают коды символов, по этой ссылке. Наличие кода не значит, что символ нельзя использовать как есть. Код просто есть как альтернатива. Например, если вы не знаете, как на клавиатуре набрать знак копирайта, можете использовать его код: ©
В целом, если вы экранировали кавычки, "<", ">" и "&", можете спать на 99.9% спокойно. Но чтобы вас не мучать дальше, просто скажу, что в PHP есть функция htmlspecialchars(), которая берёт на себя все действия по преобразованию "нехороших" символов. Так что достаточно написать:
$out[$key] = htmlspecialchars($value);
Сделаем такой тест:
При просмотре (не забыв обернуть модель в вызов функции html_convert()) всё хорошо:
Фрагмент HTML-кода страницы:
Переносы строк
Спецсимволы мы победили, но приключения не заканчиваются. Теперь попробуем такой тест:
Сохраняем, делаем просмотр, и видим, что переносов строк нет:
Смотрим в HTML и видим, что они вообще-то есть:
Так в чём же дело?
Дело в том, что HTML не считает перенос строки переносом строки. Для него это, а также пробел и табуляция – всего лишь пустое пространство (white space), которое разделяет фрагменты контента. Поэтому, чтобы в HTML отобразить перенос строки, надо заменить его на тэг <br>:
Строка 1<br>
Строка 2<br>
Чтобы не делать замену руками, воспользуемся функцией PHP nl2br(). Для преобразования модели я напишу функцию html_br(), аналогичную html_convert():
И для действия view, где нужно показывать текст, мы применим функцию html_br() после функции html_convert():
Почему после? Потому что функция html_convert() конвертирует "<" и ">", и если мы вставим тэги "<br>" до этого, они тоже окажутся сконвертированными и работать не будут.
Смотрим результат:
Ну вот, работает.
Всё дальше в хаос
Теперь немного о грустном. Я сделал две отдельных функции для конвертации, что выглядит странно. Почему нельзя всё сделать в одной? Потому что конвертация спецсимволов и переносов строк не всегда используется одновременно. Например, при редактировании заметки тот текст, который вставляется в тэг <textarea>, должен иметь обычные переносы строк, а не <br>.
Что еще хуже, у вас наступит случай, когда вам будет нужен именно неконвертированный текст. Допустим, вы написали HTML-код, и хотите, чтобы при его просмотре он был рабочим, то есть чтобы все его тэги работали как надо, а не экранировались. В этом случае конвертация при показе вообще не нужна, но при редактировании всё равно нужна.
А что еще хуже, в какой-нибудь модели часть полей может быть нужно конвертировать, а часть нет.
Всё это создаёт немыслимое количество разных специфических случаев, поэтому в каждом случае вы должны сами решать, что конвертировать и когда, и в каком порядке.
Поэтому написанные выше функции – всего лишь некая затычка. Вы можете пользоваться ими, вы можете объединить их в одну, если надо, а можете, например, индивидуально конвертировать выборочные поля модели.
Вы даже можете написать такую модель, которая будет иметь свойства полей – какое надо конвертировать, какое не надо, и т.д., и тогда это всё можно автоматизировать.
Ну а пока – вот так.
Заключение
Теперь мы можем писать нормальные тексты в заметках. Работа на этом не закончена – в дальнейшем нужно работать над стилями, новыми возможностями форм и связанными таблицами. Но это будет в отдельных тематических циклах, которые будут ссылаться на этот. А ссылки на них я также размещу здесь, в конце.