Лабораторная работа IV. HTML. CSS. Twig, формы Symfony и валидация

Лабораторная работа 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. Самостоятельные действия

  1. Измените заголовок страницы и добавьте третьего учебного клиента в таблицу.
  2. Перейдите по обеим ссылкам меню. Проверьте, что они ведут к соответствующим разделам.
  3. Отправьте форму и найдите в адресной строке параметры qty, price, discount.
  4. Попробуйте отправить пустое количество и скидку 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]

Задание

  1. Создайте:

templates/base.html.twig

  1. Создайте блоки:

title
stylesheets
body
javascripts

  1. Создайте меню:

Главная
Клиенты
Заказы
Товары
Панель управления

  1. Все страницы должны наследовать:

base.html.twig

  1. Создайте шаблоны:

customer/index.html.twig
customer/show.html.twig
customer/new.html.twig
customer/edit.html.twig
order/index.html.twig
dashboard/index.html.twig

  1. Используйте:

{% extends %}
{% block %}
{% include %}
{% for %}
{% if %}

  1. Используйте не менее пяти Twig-фильтров.

  2. Не используйте |raw для пользовательских данных.

  3. Создайте модель клиента:

name
email
phone
company
comment

  1. Создайте:

CustomerType

  1. Реализуйте:

GET /customers/new
POST /customers/new
GET /customers/{id}/edit
POST /customers/{id}/edit

  1. Добавьте validation constraints:

name:
NotBlank
Length

email:
NotBlank
Email

phone:
Length

comment:
Length

Symfony Validator использует constraints и validators для проверки данных объекта. [13]

  1. Введите максимальную длину комментария.

  2. Добавьте серверную проверку данных.

  3. Добавьте CSRF-защиту формы.

Symfony Forms по умолчанию предусматривает CSRF-защиту форм при соответствующей конфигурации FrameworkBundle. [14]

  1. После успешного создания клиента выполнить redirect.

  2. Добавьте flash-сообщение.

  3. Реализуйте фильтрацию списка клиентов:

?search=

  1. Реализуйте сортировку:

?sort=name
?sort=email

  1. Разрешите только явно заданный whitelist сортируемых полей.

  2. Создайте функциональные тесты:

открытие списка;
открытие формы;
успешное создание;
пустое имя;
ошибочный 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]


Автор: к.п.н., Румянцев Сергей Александрович, доцент Финансового университета при Правительстве РФ; доцент ОЧУВО Международного инновационного университета; Консалтинг, управление разработкой ПО; системный и бизнес анализ; менеджмент; аналитиз данных; управление ИТ. Телефон для связи +79269444818 (мессенджеры)   Короткая ссылка:

Лабораторная работа 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. Самостоятельные действия

  1. Измените заголовок страницы и добавьте третьего учебного клиента в таблицу.
  2. Перейдите по обеим ссылкам меню. Проверьте, что они ведут к соответствующим разделам.
  3. Отправьте форму и найдите в адресной строке параметры qty, price, discount.
  4. Попробуйте отправить пустое количество и скидку 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]

Задание

  1. Создайте:

templates/base.html.twig

  1. Создайте блоки:

title
stylesheets
body
javascripts

  1. Создайте меню:

Главная
Клиенты
Заказы
Товары
Панель управления

  1. Все страницы должны наследовать:

base.html.twig

  1. Создайте шаблоны:

customer/index.html.twig
customer/show.html.twig
customer/new.html.twig
customer/edit.html.twig
order/index.html.twig
dashboard/index.html.twig

  1. Используйте:

{% extends %}
{% block %}
{% include %}
{% for %}
{% if %}

  1. Используйте не менее пяти Twig-фильтров.

  2. Не используйте |raw для пользовательских данных.

  3. Создайте модель клиента:

name
email
phone
company
comment

  1. Создайте:

CustomerType

  1. Реализуйте:

GET /customers/new
POST /customers/new
GET /customers/{id}/edit
POST /customers/{id}/edit

  1. Добавьте validation constraints:

name:
NotBlank
Length

email:
NotBlank
Email

phone:
Length

comment:
Length

Symfony Validator использует constraints и validators для проверки данных объекта. [13]

  1. Введите максимальную длину комментария.

  2. Добавьте серверную проверку данных.

  3. Добавьте CSRF-защиту формы.

Symfony Forms по умолчанию предусматривает CSRF-защиту форм при соответствующей конфигурации FrameworkBundle. [14]

  1. После успешного создания клиента выполнить redirect.

  2. Добавьте flash-сообщение.

  3. Реализуйте фильтрацию списка клиентов:

?search=

  1. Реализуйте сортировку:

?sort=name
?sort=email

  1. Разрешите только явно заданный whitelist сортируемых полей.

  2. Создайте функциональные тесты:

открытие списка;
открытие формы;
успешное создание;
пустое имя;
ошибочный 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]


https://webprogr.ru/~FKLnc
Короткая ссылка на новость:https://webprogr.ru/~FKLnc


Последние новости

Лабораторная работа IV. HTML. CSS. Twig, формы Symfony и валидация

Лабораторная работа 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. Самостоятельные действия

  1. Измените заголовок страницы и добавьте третьего учебного клиента в таблицу.
  2. Перейдите по обеим ссылкам меню. Проверьте, что они ведут к соответствующим разделам.
  3. Отправьте форму и найдите в адресной строке параметры qty, price, discount.
  4. Попробуйте отправить пустое количество и скидку 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]

Задание

  1. Создайте:

templates/base.html.twig

  1. Создайте блоки:

title
stylesheets
body
javascripts

  1. Создайте меню:

Главная
Клиенты
Заказы
Товары
Панель управления

  1. Все страницы должны наследовать:

base.html.twig

  1. Создайте шаблоны:

customer/index.html.twig
customer/show.html.twig
customer/new.html.twig
customer/edit.html.twig
order/index.html.twig
dashboard/index.html.twig

  1. Используйте:

{% extends %}
{% block %}
{% include %}
{% for %}
{% if %}

  1. Используйте не менее пяти Twig-фильтров.

  2. Не используйте |raw для пользовательских данных.

  3. Создайте модель клиента:

name
email
phone
company
comment

  1. Создайте:

CustomerType

  1. Реализуйте:

GET /customers/new
POST /customers/new
GET /customers/{id}/edit
POST /customers/{id}/edit

  1. Добавьте validation constraints:

name:
NotBlank
Length

email:
NotBlank
Email

phone:
Length

comment:
Length

Symfony Validator использует constraints и validators для проверки данных объекта. [13]

  1. Введите максимальную длину комментария.

  2. Добавьте серверную проверку данных.

  3. Добавьте CSRF-защиту формы.

Symfony Forms по умолчанию предусматривает CSRF-защиту форм при соответствующей конфигурации FrameworkBundle. [14]

  1. После успешного создания клиента выполнить redirect.

  2. Добавьте flash-сообщение.

  3. Реализуйте фильтрацию списка клиентов:

?search=

  1. Реализуйте сортировку:

?sort=name
?sort=email

  1. Разрешите только явно заданный whitelist сортируемых полей.

  2. Создайте функциональные тесты:

открытие списка;
открытие формы;
успешное создание;
пустое имя;
ошибочный 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]


Рейтинг@Mail.ru