Лабораторная работа 4. HTML, CSS, Twig, формы Symfony и валидацияэ
Работа выполняется с ИИ. Здесь мы осваиваем на минимально необходимом уровне для работы с Symfony языки HTML, CSS. Далее используем эти знания для понимания использования Twig, формы Symfony и валидации. Разберите с ИИ каждую сточку своего кода, нужно понимать что для чего используется и как работает.
Часть 1
Вводный практикум по HTML
HTML: структура веб-страницы, таблицы и передача данных через формы
Цель: создать простую веб-страницу и понять, как браузер передаёт введённые пользователем данные PHP-приложению.
Результат: HTML-страница BizFlow с навигацией, таблицей и формой расчёта; понимание того, как использовать эту разметку в серверном приложении.
1.1. Что делает HTML
HTML описывает структуру и смысл содержимого страницы: заголовки, абзацы, ссылки, таблицы, поля ввода и кнопки. Документ состоит из элементов, например:
<p>Это абзац.</p> <a href="/customers">Открыть список клиентов</a> <label for="customer-name">Имя клиента</label> <input id="customer-name" name="name" type="text">
В первом примере <p> — открывающий тег, </p> — закрывающий тег, а между ними находится содержимое. Во втором href — атрибут, задающий адрес ссылки. У некоторых элементов, например <input> и <meta>, закрывающего тега нет.
В дальнейшем PHP или Symfony будут формировать HTML на сервере, а браузер — получать и отображать его. Для первого упражнения серверную обработку данных пока не реализуйте.
1.2. Подготовка локальной песочницы
В корне общего учебного репозитория создайте каталог sandbox/frontend. Он должен находиться рядом с plain-php, а не внутри vendor или src.
учебный-репозиторий/ ├── plain-php/ └── sandbox/ └── frontend/ └── index.html
Создайте файл sandbox/frontend/index.html. Сохраняйте файлы в кодировке UTF-8.
Из корня репозитория запустите отдельный учебный сервер:
php -S 127.0.0.1:8081 -t sandbox/frontend
Откройте:
http://127.0.0.1:8081/index.html
Оставьте терминал с сервером работающим. Для остановки нажмите Ctrl+C. Порт 8081 используется отдельно от порта основного приложения.
Встроенный сервер PHP предназначен для локальной разработки и учебных экспериментов, а не для размещения рабочего сайта в интернете.
1.3. Создание первой страницы
Вставьте в index.html:
<!doctype html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>BizFlow — учебная страница</title> </head> <body> <header class="site-header"> <div class="container"> <p><strong>BizFlow</strong></p> <nav aria-label="Навигация по странице"> <ul class="menu"> <li><a href="#quote">Расчёт стоимости</a></li> <li><a href="#customers">Клиенты</a></li> </ul> </nav> </div> </header> <main class="container"> <section id="quote" class="card"> <h1>Расчёт стоимости заказа</h1> <p> Введите количество, цену за единицу и скидку. В этом учебном примере цена передаётся в копейках. </p> <form id="quote-form" class="form-grid" action="/index.html" method="get" > <div class="field"> <label for="qty">Количество</label> <input id="qty" name="qty" type="number" min="1" step="1" value="10" required > </div> <div class="field"> <label for="price">Цена за единицу, коп.</label> <input id="price" name="price" type="number" min="1" step="1" value="1250" required > </div> <div class="field"> <label for="discount">Скидка, %</label> <input id="discount" name="discount" type="number" min="0" max="50" step="1" value="7" required > </div> <div class="actions"> <button type="submit">Отправить параметры</button> </div> </form> </section> <section id="customers" class="card"> <h2>Учебные клиенты</h2> <div class="table-scroll"> <table> <caption>Демонстрационные данные, не связанные с БД</caption> <thead> <tr> <th scope="col">ID</th> <th scope="col">Имя</th> <th scope="col">Email</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>Анна Смирнова</td> <td>anna@example.test</td> </tr> <tr> <td>2</td> <td>Иван Петров</td> <td>ivan@example.test</td> </tr> </tbody> </table> </div> </section> </main> <footer class="container"> <p>Учебный проект BizFlow</p> </footer> </body> </html>
Сохраните файл и обновите страницу. Пока она отображается без специального оформления — CSS будет добавлен в следующем вводном практикуме.
В этой песочнице форма отправляется обратно на статический файл index.html. Параметры появятся в адресе, но стоимость не будет рассчитана: обработчик PHP к этой странице ещё не подключён.
1.4. Разбор документа
| Элемент или атрибут | Назначение в примере |
|---|---|
<!doctype html>
|
Объявление современного HTML-документа. |
<html lang="ru">
|
Корневой элемент и основной язык страницы. |
<head>
|
Метаданные, название вкладки, подключения CSS и JavaScript. |
<body>
|
Содержимое страницы. |
<header>, <nav>, <main>, <footer>
|
Шапка, навигация, основное содержимое и подвал. |
<section>
|
Тематический раздел страницы. |
<h1>, <h2>, <p>
|
Заголовки и абзацы. |
<a href="...">
|
Ссылка на другую страницу или на элемент текущей страницы. |
<table>, <tr>, <th>, <td>
|
Таблица, строка, заголовочная ячейка и ячейка данных. |
class="card"
|
Имя класса, по которому далее можно применить CSS. |
id="quote"
|
Уникальный идентификатор элемента на странице. |
Используйте заголовки для обозначения структуры, а не только для изменения размера текста. Таблицы применяйте для табличных данных, а не для размещения всех частей страницы. Назначение HTML-элементов и структура документа описаны в документации MDN.
1.5. Как работает HTML-форма
В примере:
<form action="/index.html" method="get">
action определяет адрес получателя, а method — HTTP-метод отправки.
После отправки исходных значений адрес будет иметь вид:
http://127.0.0.1:8081/index.html?qty=10&price=1250&discount=7
Имена параметров берутся из атрибутов name, а не id. Например:
<input id="qty" name="qty" type="number" value="10">
передаёт параметр qty=10. Атрибут id нужен, в частности, для связи с подписью:
<label for="qty">Количество</label>
Значение for у подписи должно совпадать с id поля. Такая связь помогает пользоваться формой в том числе с клавиатуры и программами экранного доступа.
Различайте два сценария:
| Сценарий | Метод |
|---|---|
| Поиск, фильтрация, просмотр, расчёт без сохранения изменений |
GET
|
| Создание клиента, оформление заказа, изменение данных |
Обычно POST; в API также применяются другие методы
|
При GET значения формы передаются в строке запроса. При обычной отправке формы методом POST — в теле запроса. Сам по себе POST не шифрует данные; для защищённой передачи используется HTTPS.
Для последующих работ понадобятся и другие элементы формы:
<label for="email">Email</label> <input id="email" name="email" type="email" required> <label for="comment">Комментарий</label> <textarea id="comment" name="comment" maxlength="1000"></textarea> <label for="sort">Сортировка</label> <select id="sort" name="sort"> <option value="name">По имени</option> <option value="email">По email</option> </select>
Этот фрагмент показывает отдельные элементы. Добавлять их в форму расчёта стоимости не требуется.
Атрибуты required, min, max, maxlength и тип email помогают браузеру проверять ввод. Они не заменяют серверную валидацию: пользователь может изменить HTML или отправить запрос без формы.
1.6. Самостоятельные действия
- Измените заголовок страницы и добавьте третьего учебного клиента в таблицу.
- Перейдите по обеим ссылкам меню. Проверьте, что они ведут к соответствующим разделам.
- Отправьте форму и найдите в адресной строке параметры
qty,price,discount. - Попробуйте отправить пустое количество и скидку
51. Затем откройте инструменты разработчика браузера и найдите HTML соответствующих полей.
Для просмотра запросов откройте инструменты разработчика, вкладку Network / Сеть, и повторно отправьте форму. Найдите запрос документа и посмотрите его адрес, метод и параметры.
1.7. Применение в основной лабораторной работе
Вернитесь в проект plain-php.
Используйте созданную разметку как основу HTML-представления расчёта. В форме основного приложения измените получателя:
<form action="/quote" method="get">
Сохраните имена полей:
qty price discount
Покажите в HTML реальные результаты работы QuoteCalculator вместо демонстрационных значений. Согласуйте подпись поля цены с контрактом обработчика: рубли и копейки нельзя смешивать. В примере песочницы использовались копейки.
Пользовательские строки перед включением в HTML экранируйте. Не выводите входные параметры как готовую HTML-разметку. В последующих работах с Twig сохраняйте автоматическое экранирование и не применяйте |raw к пользовательским данным.
Работа с ИИ и результат
Передайте ИИ содержимое созданного файла:
Ты — преподаватель HTML и разработчик интерфейсов. Проверь мою учебную страницу BizFlow: структуру документа; вложенность элементов; связи label/for/id; атрибуты name; action и method формы; таблицу и заголовки; доступность с клавиатуры. Не добавляй CSS-фреймворки и JavaScript. Сначала перечисли ошибки, затем предложи минимальные исправления. Объясни, какие параметры форма отправляет серверу.
Сохраните sandbox/frontend/index.html. В docs/ai/lab-01.md добавьте использованный запрос, найденные ошибки и собственные исправления.
Самопроверка: страница открывается через локальный сервер; ссылки работают; поля имеют подписи; GET-параметры видны в запросе; студент может объяснить различия между id, class и name.
Часть 2
Вводный практикум по CSS
CSS: оформление страницы, формы и адаптивная компоновка
Цель: оформить HTML-страницу BizFlow и подключить общие стили к Symfony-приложению.
Результат: читаемый интерфейс с меню, формой и таблицей, пригодный для просмотра на широком и узком экране.
2.1. Что делает CSS
CSS определяет внешний вид HTML: шрифты, цвета, размеры, отступы, границы и размещение элементов. Для этого используются правила вида:
.card { padding: 1rem; background-color: white; }
Здесь .card — селектор, выбирающий элементы с class="card", padding — свойство, а 1rem — его значение.
В практикуме используйте обычный CSS без Bootstrap, Tailwind и других библиотек.
2.2. Подключение файла стилей
Продолжите работу с песочницей из HTML-практикума:
sandbox/ └── frontend/ ├── index.html └── styles.css
Создайте styles.css.
В index.html, внутри <head> после <title>, добавьте:
<link rel="stylesheet" href="styles.css">
Запустите песочницу, если сервер остановлен:
php -S 127.0.0.1:8081 -t sandbox/frontend
Откройте:
http://127.0.0.1:8081/index.html
2.3. Основные стили
Добавьте в styles.css:
/* Размеры элементов включают внутренние отступы и границы. */ * { box-sizing: border-box; } body { margin: 0; font-family: system-ui, sans-serif; font-size: 1rem; line-height: 1.5; color: #1f2937; background-color: #f3f4f6; } /* Ограничиваем ширину содержимого и центрируем его. */ .container { width: 92%; max-width: 64rem; margin: 0 auto; } .site-header { padding: 1rem 0; background-color: #ffffff; border-bottom: 1px solid #d1d5db; } .menu { display: flex; flex-wrap: wrap; gap: 1rem; padding: 0; list-style: none; } a { color: #174ea6; } .card { margin: 1rem 0; padding: 1.5rem; background-color: #ffffff; border: 1px solid #d1d5db; border-radius: 0.5rem; } /* На широком экране поля располагаются в две колонки. */ .form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; } .field { display: grid; gap: 0.35rem; } .field input, .field select, .field textarea { width: 100%; min-width: 0; padding: 0.65rem; border: 1px solid #6b7280; border-radius: 0.25rem; font: inherit; } .actions { grid-column: 1 / -1; } button { padding: 0.7rem 1rem; border: 1px solid #174ea6; border-radius: 0.25rem; font: inherit; color: #ffffff; background-color: #174ea6; cursor: pointer; } button:hover { background-color: #103b80; } button:disabled { opacity: 0.65; cursor: wait; } /* Не скрываем выделение элемента при работе с клавиатуры. */ a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 3px solid #9a3412; outline-offset: 3px; } .table-scroll { overflow-x: auto; } table { width: 100%; border-collapse: collapse; } caption { margin-bottom: 0.5rem; text-align: left; } th, td { padding: 0.75rem; border-bottom: 1px solid #d1d5db; text-align: left; } .message { min-height: 1.5em; } .message--error { color: #b91c1c; } /* На узком экране форма становится одноколоночной. */ @media (max-width: 40rem) { .form-grid { grid-template-columns: 1fr; } .card { padding: 1rem; } }
Обновите страницу. Сравните её вид до и после подключения файла.
2.4. Какие механизмы использованы
Селекторы. Правило body выбирает элемент по имени тега, .card — по классу, #quote выбрало бы элемент по идентификатору. Селектор .field input выбирает поля ввода внутри элементов класса field. Псевдоклассы, например :hover и :disabled, позволяют учитывать состояние элемента.
Отступы и размеры. margin задаёт внешний отступ, padding — внутренний, border — границу. При box-sizing: border-box заданная ширина включает внутренние отступы и границы. Единица rem связана с размером шрифта корневого элемента документа.
Размещение элементов. Flexbox используется для меню: ссылки располагаются в ряд и при необходимости переносятся. Grid задаёт колонки формы. gap устанавливает промежутки между элементами.
Адаптивность. Правило @media меняет компоновку при небольшой ширине окна. Строка meta name="viewport" из HTML-практикума необходима для корректного поведения такой компоновки на мобильных устройствах.
Каскад. Не считайте, что любое правило ниже в файле всегда сильнее предыдущего. На выбор стиля влияют несколько факторов, включая специфичность селектора. Порядок записи решает конфликт только при равенстве предшествующих условий. В этом практикуме не используйте !important для маскировки ошибок оформления.
2.5. Проверка оформления
Откройте инструменты разработчика браузера.
Во вкладке Elements / Элементы выберите карточку .card и найдите её стили. Поочерёдно измените padding, margin и border. Объясните, что изменилось.
Во вкладке Network / Сеть найдите styles.css. Файл должен загружаться без ошибки 404.
Проверьте страницу при ширине окна приблизительно 1280, 768 и 360 пикселей. Поля должны оставаться доступными, меню — переноситься, а широкая таблица — прокручиваться внутри своего контейнера.
Пройдите по ссылкам и полям клавишей Tab. У активного элемента должна оставаться видимая рамка фокуса.
2.6. Подключение CSS к Symfony
После проверки песочницы скопируйте её файл стилей в Symfony-проект:
bizflow/ └── public/ └── css/ └── frontend-basics.css
В создаваемом templates/base.html.twig подключите его внутри блока stylesheets:
{% block stylesheets %} <link rel="stylesheet" href="/css/frontend-basics.css"> {% endblock %}
Если блок уже существует, добавьте <link> внутрь него, не удаляя существующее содержимое. Не создавайте второй одноимённый блок.
В этом учебном варианте приложение открывается от корня сайта. Поэтому файл:
bizflow/public/css/frontend-basics.css
доступен браузеру по пути:
/css/frontend-basics.css
а не по пути /public/css/frontend-basics.css. Symfony использует public/ как публичный каталог; стили и скрипты могут обслуживаться как статические ресурсы.
Перенесите в Twig-шаблоны классы из песочницы:
<section class="card"> <h1>Клиенты</h1> <p>Здесь будет список клиентов BizFlow.</p> </section>
Для форм используйте контейнеры form-grid, field и actions, для таблиц — table-scroll. Одинаковый CSS должен применяться ко всем страницам, наследующим общий шаблон.
Если в проекте уже настроен AssetMapper, сохраните его подключения. Для этого вводного упражнения используется отдельный CSS-файл в public/; не подключайте один и тот же файл одновременно двумя способами. Средства управления ресурсами Symfony рассматриваются отдельно от базового синтаксиса CSS.
2.7. Работа с ИИ и результат
Ты — разработчик интерфейсов Symfony/Twig. Проверь HTML и CSS моего BizFlow: подключение файла стилей; селекторы и повторяющиеся правила; отступы и размеры; отображение формы на узком экране; прокрутку таблицы; видимость клавиатурного фокуса. Не устанавливай CSS-фреймворки. Не переписывай структуру приложения. Сначала объясни найденные проблемы, затем предложи минимальный патч.
Сохраните sandbox/frontend/styles.css, файл bizflow/public/css/frontend-basics.css и изменения Twig-шаблонов. В docs/ai/lab-04.md добавьте результаты проверки.
Самопроверка: файл CSS загружается; стили применяются через общий шаблон; форма перестраивается на узком экране; таблица доступна; студент может объяснить селектор, margin, padding, Flexbox и назначение @media.
Часть 3
Цель: реализовать пользовательский интерфейс BizFlow.
Twig поддерживает наследование шаблонов, выражения, фильтры, функции и автоматическое экранирование; Symfony Forms объединяет построение, обработку, отображение и валидацию форм. [12]
Задание
-
Создайте:
templates/base.html.twig
-
Создайте блоки:
title
stylesheets
body
javascripts
-
Создайте меню:
Главная
Клиенты
Заказы
Товары
Панель управления
-
Все страницы должны наследовать:
base.html.twig
-
Создайте шаблоны:
customer/index.html.twig
customer/show.html.twig
customer/new.html.twig
customer/edit.html.twig
order/index.html.twig
dashboard/index.html.twig
-
Используйте:
{% extends %}
{% block %}
{% include %}
{% for %}
{% if %}
-
Используйте не менее пяти Twig-фильтров.
-
Не используйте |raw для пользовательских данных.
-
Создайте модель клиента:
name
email
phone
company
comment
-
Создайте:
CustomerType
-
Реализуйте:
GET /customers/new
POST /customers/new
GET /customers/{id}/edit
POST /customers/{id}/edit
-
Добавьте validation constraints:
name:
NotBlank
Length
email:
NotBlank
Email
phone:
Length
comment:
Length
Symfony Validator использует constraints и validators для проверки данных объекта. [13]
-
Введите максимальную длину комментария.
-
Добавьте серверную проверку данных.
-
Добавьте CSRF-защиту формы.
Symfony Forms по умолчанию предусматривает CSRF-защиту форм при соответствующей конфигурации FrameworkBundle. [14]
-
После успешного создания клиента выполнить redirect.
-
Добавьте flash-сообщение.
-
Реализуйте фильтрацию списка клиентов:
?search=
-
Реализуйте сортировку:
?sort=name
?sort=email
-
Разрешите только явно заданный whitelist сортируемых полей.
-
Создайте функциональные тесты:
открытие списка;
открытие формы;
успешное создание;
пустое имя;
ошибочный email;
слишком длинный комментарий;
CSRF.
Обязательный запрос ИИ-агенту интерфейса
Ты — Symfony/Twig developer.
Нужно создать интерфейс управления клиентами.
Используй:
base.html.twig;
template inheritance;
partials;
Symfony Form;
Symfony Validator;
flash messages.
Не добавляй JavaScript framework.
Не используй |raw для пользовательского контента.
Сначала покажи структуру templates/.
После подтверждения структуры реализуй шаблоны и формы.
Обязательный запрос ИИ-тестировщику
Проведи негативное тестирование формы CustomerType.
Проверь:
пустые строки;
невалидный email;
очень длинные значения;
HTML;
строку <script>alert(1)</script>;
неверный CSRF token;
неизвестные поля.
Создай functional tests.
Запусти их и исправь найденные проблемы.
Самопроверка
php bin/console lint:twig templates
php bin/console lint:yaml config
php bin/console debug:validator
php bin/phpunit
Критерии приемки
[ ] используется Twig inheritance
[ ] имеется общий layout
[ ] форма создается через Symfony Form
[ ] работает Validator
[ ] имеется CSRF
[ ] XSS-тест не приводит к выполнению HTML/JS
[ ] реализованы flash messages
[ ] проходят functional tests
[ ] имеется AI log
Symfony Forms обрабатывает отображение и submission форм, а Validator применяет constraints к данным до их сохранения. [15]
