<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 22:57:27 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="j_Dh-1Elk30lbft0tbaHZeXtVG8xAYPeUGdcQskKfDNgISrMo1s-HZMu3-y5uXcSJeR5xTk2fXzth8YWmw2bXQ";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Модифицирующие формы | Веб-разработка на PHP</title>
<meta name="description" content="Модифицирующие формы / Веб-разработка на PHP: Учимся изменять данные на сервере с помощью форм">
<link rel="canonical" href="https://ru.hexlet.io/courses/php-mvc/lessons/post-form/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Модифицирующие формы">
<meta property="og:title" content="Веб-разработка на PHP">
<meta property="og:description" content="Модифицирующие формы / Веб-разработка на PHP: Учимся изменять данные на сервере с помощью форм">
<meta property="og:url" content="https://ru.hexlet.io/courses/php-mvc/lessons/post-form/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="6i4pv2IRLj3mbqguSztCZ8zCYlquuGzytVo8hrWeHV8F_-KIkG-DXVAtjLZHNLIQDMtP8KaPklAIuqbS55n6MQ" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk5MiwicHVyIjoiYmxvYl9pZCJ9fQ==--e9d0f30948ea766a7e6bc3e3d56c192344d45fb8/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programming-cuate%20(1).png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T22:57:26.980Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"0o7LW2u3Z-c2U30XhoZp8aDWH6xYdlhMzI4yXMDzhX49XwBsmcnKh4AQWY-KiZmGYN8yBlBBpu5xbqgIkvRiEA","topics":[{"id":42987,"title":"Добрый день.\nПодскажите, почему с решением учителя тесты проходят. Но при заходе на 'courses' появляется ошибка\n\n```\n405 Method Not Allowed\nThe application could not run because of the following error:\n\nDetails\nType: Slim\\Exception\\HttpMethodNotAllowedException\nCode: 405\nMessage: Method not allowed. Must be one of: POST\nFile: /composer/vendor/slim/slim/Slim/Middleware/RoutingMiddleware.php\nLine: 96\n```","plain_title":"Добрый день. Подскажите, почему с решением учителя тесты проходят. Но при заходе на 'courses' появляется ошибка 405 Method Not Allowed The application could not run because of the following error: Details Type: Slim\\Exception\\HttpMethodNotAllowedException Code: 405 Message: Method not allowed. Must be one of: POST File: /composer/vendor/slim/slim/Slim/Middleware/RoutingMiddleware.php Line: 96 ","creator":{"public_name":"Алексей Довгань","id":284659,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":93573,"body":"Приветствую, Алексей!\n\nТолько что проверил, не вижу ошибок. Попробуйте выполнить сброс упражнения, и заново проверить решение учителя. Напишите потом, что у вас получилось.","topic_id":42987}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Модифицирующие формы","entity_url":null,"active":true}},{"id":49593,"title":"Снова я пишу, извиняюсь что новый топик создал, но уже вопрос по самостоятельной работе) Вот выполнил все что требуется в самостоятельной работе, но не понимаю зачем извлекать данные из файла? Т.е создали шаблон, пользователь ввел данные, мы сохранили его в файл, далее редирект.\n\nНемного еще не врубился как создать id, если в шаблоне его устанавливать, то каждый раз там будет считать по-новому. Делал с рандомом, понимаю, что это неверно, но вечером больше толковых идей не пришло.","plain_title":"Снова я пишу, извиняюсь что новый топик создал, но уже вопрос по самостоятельной работе) Вот выполнил все что требуется в самостоятельной работе, но не понимаю зачем извлекать данные из файла? Т.е создали шаблон, пользователь ввел данные, мы сохранили его в файл, далее редирект. Немного еще не врубился как создать id, если в шаблоне его устанавливать, то каждый раз там будет считать по-новому. Делал с рандомом, понимаю, что это неверно, но вечером больше толковых идей не пришло. ","creator":{"public_name":"Ди Анжело","id":283608,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":106390,"body":"Приветствую!\n\nИзвлекать данные нужно для того, чтобы, например, красиво вывести всех добавленных пользователей. Или чтобы понять на каком id мы остановились и какой id будет у следующего пользователя. Чтобы легко можно было работать с данными кодируйте и декодируйте их в json и обратно с помощью соответствующих функций о которых говорится в самостоятельной работе.","topic_id":49593}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Модифицирующие формы","entity_url":null,"active":true}},{"id":51552,"title":"Здравствуйте! Два момента по самостоятельной работе:\n1) так мы продолжаем дописывать файл index.php новыми маршрутами, то при добавлении в самом конце нового маршрута $app->get('/users/new'... и имея перед ним маршрут $app->get('/users/{id}'... - возникает ошибка с подменой маршрутов (путем проб и ошибок пришлось сначала поставить маршрут '/users/new', так как видимо Slim ищет их по мере совпадения похожих названий)\n2) если вводить например в поле никнейма имя на русском языке - все ломается (видимо из-за работы туда-обратно с json данными).\nвозможно очевидно, но может кому-нибудь пригодиться)\n","plain_title":"Здравствуйте! Два момента по самостоятельной работе: 1) так мы продолжаем дописывать файл index.php новыми маршрутами, то при добавлении в самом конце нового маршрута $app->get('/users/new'... и имея перед ним маршрут $app->get('/users/{id}'... - возникает ошибка с подменой маршрутов (путем проб и ошибок пришлось сначала поставить маршрут '/users/new', так как видимо Slim ищет их по мере совпадения похожих названий) 2) если вводить например в поле никнейма имя на русском языке - все ломается (видимо из-за работы туда-обратно с json данными). возможно очевидно, но может кому-нибудь пригодиться) ","creator":{"public_name":"Александр","id":234930,"is_tutor":false},"comments":[{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":110114,"body":"Приветствую, Александр!\n\nСпасибо! Уверен, что топик будет полезен другим студентам.","topic_id":51552},{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":110593,"body":"Если я правильно понял ваш вопрос, то предлагаю попробовать проанализировать каким образом тесты взаимодействуют с приложением, а затем открыть вариант из код-ревью в веб-доступе и попробовать добавить платный курс. Тесты отправляют данные с помощью запросов в массиве, а вот человек при взаимодействии с приложением использует его интерфейс.\n\nТакже обратите внимание, что в тестах проверяется валидация, а не тот момент платный курс или нет. ","topic_id":51552},{"creator":{"public_name":"Александр","id":234930,"is_tutor":false},"id":110559,"body":"Пардон, забыл https://ru.hexlet.io/code_reviews/369034.\nИ еще один момент: даже при записи <option>-ов тесты проходят при простом написании в них <?= $course['paid'] = '1' ?> и т.д. Может тернарный оператор в них и не нужен?","topic_id":51552},{"creator":{"public_name":"Александр","id":234930,"is_tutor":false},"id":110558,"body":"И снова, здравствуйте! Тесты проходят, хотя в new.phtml не прописано \"половина\" кода - прошу пояснить и разобраться) Спасибо","topic_id":51552}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Модифицирующие формы","entity_url":null,"active":true}},{"id":43674,"title":"К теории. Правильно ли я понимаю редирект, когда пишем\nreturn $response->withRedirect('/users');\nТо в этом месте ищется маршрут GET /users, которого у нас еще нет, значит необходимо написать отдельный обработчик для GET /users и отдельный шаблон?","plain_title":"К теории. Правильно ли я понимаю редирект, когда пишем return $response->withRedirect('/users'); То в этом месте ищется маршрут GET /users, которого у нас еще нет, значит необходимо написать отдельный обработчик для GET /users и отдельный шаблон? ","creator":{"public_name":"Вильдан Фазлыев","id":252958,"is_tutor":false},"comments":[{"creator":{"public_name":"Вильдан Фазлыев","id":252958,"is_tutor":false},"id":94866,"body":"**Nikolai Gagarinov**, вопрос относится не к практике урока, а к теории. В теории описан маршрут POST /users, а GET нет, а данная функция видимо выполняется именно с GET?","topic_id":43674},{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":94859,"body":"Привет, да, возвращается ответ с заголовком редиректа.\nНет, метод уже реализован и есть в index.php","topic_id":43674},{"creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"id":94911,"body":"Да, у нас должен быть живой роут.\nТочнее там может быть не только роут, но и вообще любая ссылка, например внешний сайт.\nhttps://developer.mozilla.org/ru/docs/Web/HTTP/Redirections","topic_id":43674}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Модифицирующие формы","entity_url":null,"active":true}},{"id":17253,"title":">По традции микрофреймворки","plain_title":"По традции микрофреймворки ","creator":{"public_name":"Nikolai Gagarinov","id":104929,"is_tutor":true},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":36425,"body":"Спасибо, поправил!","topic_id":17253}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Модифицирующие формы","entity_url":null,"active":true}},{"id":41321,"title":"Добрый вечер, подскажите пжлст, как вернуть код 422 в случае ошибки валидации?\n\nhttps://ru.hexlet.io/code_reviews/250789","plain_title":"Добрый вечер, подскажите пжлст, как вернуть код 422 в случае ошибки валидации? https://ru.hexlet.io/code_reviews/250789 ","creator":{"public_name":"Евгений Вафиев","id":255088,"is_tutor":false},"comments":[{"creator":{"public_name":"Евгений Вафиев","id":255088,"is_tutor":false},"id":90593,"body":"**Kirill Mokevnin**, все, разобрался)\n```\n $response = $response->withStatus(422);\n```\n\nСпойлер был в уроке _Веб-разработка на PHP → CRUD: Создание_","topic_id":41321},{"creator":{"public_name":"Евгений Вафиев","id":255088,"is_tutor":false},"id":90373,"body":"**Kirill Mokevnin**, да, я это понимаю, тоже видел в этом ошибку...про имутабельность не вник полностью, попробую завтра еще подход на свежую голову","topic_id":41321},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":90362,"body":"Вот в этом уроке рассказывалось про то какие есть способы работы с запросом и ответом: https://ru.hexlet.io/courses/php-mvc/lessons/psr7/theory_unit\n\nТам описан метод `withStatus`, который вам и нужен.","topic_id":41321},{"creator":{"public_name":"Евгений Вафиев","id":255088,"is_tutor":false},"id":90489,"body":"**Kirill Mokevnin**, сформулировал более точно вопрос. У нас следующие ситуации: если нет ошибок ВОЗВРАЩАЕМ редирект, если есть ошибки, то надо ВЕРНУСТЬ форму и статус 402. То есть один из исходов требует вернуть 2 значения. Как это реализовать. Через флюент интерфейс? тогда какой порядок и важен ли он вообще ","topic_id":41321},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":90374,"body":"В вашем коде вы вызываете метод `withStatus` и ничего с этим не делаете. А он не меняет `response`, этот метод возвращает новый `response`. Соответственно просто вызов этого метода бесполезен.","topic_id":41321},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":90521,"body":"Это тот момент где надо наработать практику. В любом случае всегда можно поговорить в комьюнити, это часто помогает)","topic_id":41321},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":90518,"body":"Тогда нет ничего проще чем практиковать http. Вы ведь прошли курс, знаете как делаются запросы с помощью телнета. Попробуйте теперь поделать все это локально. Возьмите сайт, походите по его страницам используя telnet и curl. Разберитесь окончательно в том как выглядит запрос и как ответ. Откройте любой сайт в браузере, откройте в дев тулзах вкладку network, посмотрите какие запросы делает браузер и что возвращают сайты.","topic_id":41321},{"creator":{"public_name":"Евгений Вафиев","id":255088,"is_tutor":false},"id":90514,"body":"**Kirill Mokevnin**, ну вот у меня небольшое непонимание именно в работе http. Понимаю что все построено на связках запрос-ответ, но какое то фундаментальное понимание не приходит, поэтому и писал в другом топике, что зачастую на ощупь иду. Ладно, буду разбираться практиковать и пробовать, может че придумаю )) ","topic_id":41321},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":90511,"body":"> тогда какой порядок и важен ли он вообще\n\nТут важно понимать как все же работает HTTP. Во что превратиться ваш ответ, который формируется внутри фреймворка? Может ли как-то фреймворк влиять на то как работает HTTP протокол? Из этого можно сделать вывод важен ли порядок. А кроме того, когда что-то вызывает сомнения, то нужно просто экспериментировать. Именно так работают разработчики. Знать заранее ответы на все вопросы невозможно.","topic_id":41321},{"creator":{"public_name":"Евгений Вафиев","id":255088,"is_tutor":false},"id":90520,"body":"**Kirill Mokevnin**, хорошо будем пробовать) с деревьями так же все проклинал но в конце все таки понял как это делается ) думаю и тут разберусь, спасибо ","topic_id":41321},{"creator":{"public_name":"Евгений Вафиев","id":255088,"is_tutor":false},"id":90371,"body":"**Kirill Mokevnin**, это понятго) \nЯ исходя из моего ревью. Как в уроке реализовал, но что то идет не так, не пойму где ошибся ","topic_id":41321},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":90372,"body":"А ну тут нужно вспомнить что response имутабельный. Там в теории был пример того как правильно задавать код ответа. В вашем коде написано по другому. И я напомню что если делается редирект, то у ответа не может быть рендеринга шаблона. Редирект просто отправляет на другой адрес.","topic_id":41321},{"creator":{"public_name":"Евгений Вафиев","id":255088,"is_tutor":false},"id":90327,"body":"@mentors, никто не сможет подсказать?)","topic_id":41321}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Модифицирующие формы","entity_url":null,"active":true}},{"id":31910,"title":"У меня вопрос по домашнему заданию. <form action='URL'> при нажатии на кнопку в этой форме, должен происходить переход на это самый URL? Если да, то почему у меня не переходит? у меня есть адрес localhost:8000/users и localhost:8000/users/new. На /new эта форма и выводится, action='/users' нажимаю на кнопку, страница просто обновляется. Я неправильно адрес указываю? Вроде с ссылками так работало в предыдущих уроках. Еще такой вопрос ```$app->post('/users', function... ``` я правильно понимаю, что маршрут и URL должны совпадать, чтобы обработать запрос?","plain_title":"У меня вопрос по домашнему заданию. при нажатии на кнопку в этой форме, должен происходить переход на это самый URL? Если да, то почему у меня не переходит? у меня есть адрес localhost:8000/users и localhost:8000/users/new. На /new эта форма и выводится, action='/users' нажимаю на кнопку, страница просто обновляется. Я неправильно адрес указываю? Вроде с ссылками так работало в предыдущих уроках. Еще такой вопрос $app->post('/users', function... я правильно понимаю, что маршрут и URL должны совпадать, чтобы обработать запрос? ","creator":{"public_name":"Руслан Куга","id":206931,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":69701,"body":"> прошли тесты без этого, почему?\n\nВидимо они не проверяют отмеченность.\n\n> есть ли разница между count и empty? именно в качестве проверки. как отличаются функции я понимаю.\n\nempty семантичнее, так как здесь сразу правильный вопрос, а не косвенная проверка\n\n> Кстати, в браузере адрес меняется с users/new на users, но форма, которая должна быть на users/new остается и тут в задании проверил, такая же ситуация. почему так?\n\nВ обработчике она явно рендерится.\n\n\n","topic_id":31910},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":69318,"body":"> нажимаю на кнопку, страница просто обновляется\n\n1. По умолчанию формы отправляются GET запросом. Это подходит для поисковых форм, но не для модифицирующих форм.\n2. В action точно правильный адрес? Вы смотрели исходный код страницы в браузере?\n\n> я правильно понимаю, что маршрут и URL должны совпадать, чтобы обработать запрос?\n\nМаршрут это не url, это паттерн. Один маршрут может обрабатывать множество урлов. В некоторых, простых случаях маршрут не содержит динамических частей и соответствует одному урлу.","topic_id":31910},{"creator":{"public_name":"Руслан Куга","id":206931,"is_tutor":false},"id":69662,"body":"Мощное задание получилось. 2 часа ушло + около 2 на домашнее задание. но пара вопросов осталось все таки без ответа.\n1) ```<?= isset($course['paid']) && $course['paid'] ? 'selected' : '' ?>``` прошли тесты без этого, почему?\n2) есть ли разница между count и empty? именно в качестве проверки. как отличаются функции я понимаю.\n3) я совсем забыл про $repo->save(). проверки все равно прошли.\n\nКстати, в браузере адрес меняется с users/new на users, но форма, которая должна быть на users/new остается и тут в задании проверил, такая же ситуация. почему так? \nТолько сейчас дошло. это ведь из-за того, что шаблон указан courses/new.phtml","topic_id":31910}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Модифицирующие формы","entity_url":null,"active":true}},{"id":25518,"title":"**?? ''** - это очепятки?\n\n`// removed`","plain_title":"?? '' - это очепятки? // removed ","creator":{"public_name":"Валентин Иванов","id":133349,"is_tutor":false},"comments":[{"creator":{"public_name":"Валентин Иванов","id":133349,"is_tutor":false},"id":54604,"body":"Было бы неплохо в теории отобразить наличие такого оператора в теории (если я это не пропустил)","topic_id":25518},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":54777,"body":"Валентин, добавил ссылку на документацию в дополнительных материалах.","topic_id":25518},{"creator":{"public_name":"Nikita Mikhaylov","id":186965,"is_tutor":true},"id":54595,"body":"Это `Null-коалесцентный оператор`, в случае с `$user['name'] ?? ''` он проверяет на существование $user['name']. Если значение существует, то подставляет его, иначе то, что находится справа от ??. Если проще, то это можно записать в таком виде:\n\n\n`htmlspecialchars(isset($user['name']) ? $user['name'] : '');`\n","topic_id":25518}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Модифицирующие формы","entity_url":null,"active":true}},{"id":49418,"title":"При сабмите формы получаю ошибку: **Method not allowed. Must be one of: GET**\n```\nType: Slim\\Exception\\HttpMethodNotAllowedException\nCode: 405\nMessage: Method not allowed. Must be one of: GET\nFile: /composer/vendor/slim/slim/Slim/Middleware/RoutingMiddleware.php\nLine: 96\n```\n\nНе понимаю, что нужно исправить. \n\n```$app->get('/courses/new', function ($request, $response) use ($repo) { ``` \n\n```<form aciton=\"/courses\" method=\"post\">```\n\nhttps://ru.hexlet.io/code_reviews/339988","plain_title":"При сабмите формы получаю ошибку: Method not allowed. Must be one of: GET Type: Slim\\Exception\\HttpMethodNotAllowedException Code: 405 Message: Method not allowed. Must be one of: GET File: /composer/vendor/slim/slim/Slim/Middleware/RoutingMiddleware.php Line: 96 Не понимаю, что нужно исправить. $app->get('/courses/new', function ($request, $response) use ($repo) { <form aciton=\"/courses\" method=\"post\"> https://ru.hexlet.io/code_reviews/339988 ","creator":{"public_name":"Evgeny Pavlov","id":181127,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":106033,"body":"Евгений, так об обработчике post я вам писал и в первом своём ответе и во втором с примером.\n\n> Решение прошло проверку, но в браузере я все равно получаю ту же ошибку.\n\nВ имени атрибута action содержится опечатка `<form aciton=\"/courses\"`. Её нужно поправить и ошибка пропадёт.","topic_id":49418},{"creator":{"public_name":"Evgeny Pavlov","id":181127,"is_tutor":false},"id":106027,"body":"Я понял.\n\n```$app->get('/courses'```\n\n```$app->post('/courses'```\n\nЭто разные обработчики, соответствующим образом исправил код. Решение прошло проверку. Спасибо. ","topic_id":49418},{"creator":{"public_name":"Evgeny Pavlov","id":181127,"is_tutor":false},"id":106026,"body":"Я вроде нашел ответ\nhttps://github.com/slimphp/Slim/issues/1579#issuecomment-283308286\n> A redirect is always a GET request. You have made /checkParams a POST, so it won't match.\n\nТ.е. проблема вот в этом редиректе:\n```return $response->withRedirect('/courses', 302);```\n\nОн всегда GET, а у меня данные приходят в обработчик POST.\n\nКак теперь это исправить? Уверены, что все верно в задании и в окружающем коде?","topic_id":49418},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":106012,"body":"Приветствую, Евгений!\n\nПри отправке форме шлётся post запрос на адрес `/courses`, а обработчика такого маршрута сейчас в коде нет. При переходе на `/courses/new`, должна только рендерится форма, а обработка формы должна происходить в другом обработчике.","topic_id":49418},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":106022,"body":"Евгений, так а почему обработчик get? Отправка формы должна происходить по адресу `/courses` с использованием метода POST. Соответственно должен быть обработчик post. То есть `$app->post('/courses', ....`","topic_id":49418},{"creator":{"public_name":"Evgeny Pavlov","id":181127,"is_tutor":false},"id":106025,"body":"Обработчик `/courses` я не могу реализовать, т.к. он уже реализован за пределами `// BEGIN` и `// END`. \n\nЯ реализовал обработчик `/course`. \n\n```$app->post('/course', function ($request, $response) use ($repo) { ```\n\n```<form aciton=\"/course\" method=\"post\">```\n\nПри сабмите формы получаю ту же ошибку. \n\n**Message: Method not allowed. Must be one of: GET**","topic_id":49418},{"creator":{"public_name":"Evgeny Pavlov","id":181127,"is_tutor":false},"id":106021,"body":"Спасибо за оперативный ответ. \n\nЯ исправил решение. А именно:\n1) Добавил новый обработчик `$app->get('/course'`\n2) Заменил `action` в форме на `/course`\n3) Исправил соответственно код обработчиков.\n\nВ результате получаю ту же ошибку. \n\nВ описании указано следующее:\n> Реализуйте создание курсов в которое входит два обработчика /courses/new (отображает форму) и /courses создает курс.\n\nНо за пределами `// BEGIN` и `// END` уже существует обраотчик `/courses`, соответсовенно дубликат я реализовать не могу, поэтому использую обработчик `/course`. \n\nПодскажите в каком направлении двигаться.\n\nhttps://ru.hexlet.io/code_reviews/339988","topic_id":49418},{"creator":{"public_name":"Evgeny Pavlov","id":181127,"is_tutor":false},"id":106028,"body":"Решение прошло проверку, но в браузере я все равно получаю ту же ошибку.\n\nhttps://ru.hexlet.io/code_reviews/339988","topic_id":49418}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Модифицирующие формы","entity_url":null,"active":true}},{"id":49505,"title":"Добрый вечер, не могу понять как передать одновременно и код запроса и чтобы форма оставалась. Ниже ссылка на ревью. И еще вопрос.\n- Как отлаживать такой код, в дебаге нельзя посмотреть. Или удобнее вначале писать где то код (например циклы) и если все впорядке вписывать в форму?\n\n[Ревью](https://ru.hexlet.io/code_reviews/340897)","plain_title":"Добрый вечер, не могу понять как передать одновременно и код запроса и чтобы форма оставалась. Ниже ссылка на ревью. И еще вопрос. - Как отлаживать такой код, в дебаге нельзя посмотреть. Или удобнее вначале писать где то код (например циклы) и если все впорядке вписывать в форму? Ревью (https://ru.hexlet.io/code_reviews/340897) ","creator":{"public_name":"Ди Анжело","id":283608,"is_tutor":false},"comments":[{"creator":{"public_name":"Ди Анжело","id":283608,"is_tutor":false},"id":106143,"body":"Спасибо большое, \n> Метод withStatus() можно вызывать непосредственно на $response в момент передачи в render(). Ветка else тут точно нужна? Ниже мы ведь как раз передаём $response в render() и возвращаем нужный view.\n\nТак делал, но из-за моей невнимательности, а именно синтаксической ошибки и не работало. Я уже проблему в другом искал)","topic_id":49505},{"creator":{"public_name":"Roman Ashikov","id":226258,"is_tutor":true},"id":106112,"body":"Приветствую!\n\nКажется тут опечатка, проверьте, пожалуйста.\n\n 'course' => $courses\n\nМетод `withStatus()` можно вызывать непосредственно на `$response` в момент передачи в `render()`. Ветка `else` тут точно нужна? Ниже мы ведь как раз передаём `$response` в `render()` и возвращаем нужный view.\n\nМожно выводить значения переменных с помощью `var_dump()` и они будут распечатываться в web-доступе. Попробуйте, например, распечатать массив `$params` и в web-доступе не заполнить какое-то из полей при создании курса. Также Slim распечатывает стек и ошибку аналогично в web-доступе. В полноценных фреймворках, например, в Laravel (который рассматривается далее в профессии) из коробки работает логирование, что значительно упрощает отладку.","topic_id":49505}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Модифицирующие формы","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":760,"slug":"php_mvc_post_form_exercise","name":null,"state":"active","kind":"exercise","language":"php","locale":"ru","has_web_view":true,"has_test_view":false,"reviewable":true,"readme":"## src/Validator.php\n\nРеализуйте класс валидатор, который проверяет данные курса. Реализация должна соответствовать интерфейсу `ValidatorInterface`.\n\nВалидации:\n\n* Свойство `paid` - должно быть заполнено\n* Свойство `title` - должно быть заполнено\n\nЕсли поле не заполнено, то используется сообщение *Can't be blank*\n\n## public/index.php\n\nРеализуйте создание курсов в которое входит два обработчика */courses/new* (отображает форму) и */courses* создает курс.\n\nЕсли данные формы валидны, то сохраните курс `$repo->save($course)` и выполните редирект на страницу со списком курсов */courses*. Если данные не валидны, то выведите форму с заполненными полями и сообщения об ошибках.\n\n## templates/courses/new.phtml\n\nВыведите форму создания курса со следующими полями:\n\n* *paid* - селект определяющий платность курса (true/false)\n* *title* - имя курса\n\n### Подсказки\n\n* В случае ошибок валидации нужно возвращать код 422\n* При именовании полей в форме (аттрибут *name*) используйте схему, которая описана и показана в теории. Все данные формы должны попадать в один массив, именем которого является имя сущности.\n","prepared_readme":"## src/Validator.php\n\nРеализуйте класс валидатор, который проверяет данные курса. Реализация должна соответствовать интерфейсу `ValidatorInterface`.\n\nВалидации:\n\n* Свойство `paid` - должно быть заполнено\n* Свойство `title` - должно быть заполнено\n\nЕсли поле не заполнено, то используется сообщение *Can't be blank*\n\n## public/index.php\n\nРеализуйте создание курсов в которое входит два обработчика */courses/new* (отображает форму) и */courses* создает курс.\n\nЕсли данные формы валидны, то сохраните курс `$repo->save($course)` и выполните редирект на страницу со списком курсов */courses*. Если данные не валидны, то выведите форму с заполненными полями и сообщения об ошибках.\n\n## templates/courses/new.phtml\n\nВыведите форму создания курса со следующими полями:\n\n* *paid* - селект определяющий платность курса (true/false)\n* *title* - имя курса\n\n### Подсказки\n\n* В случае ошибок валидации нужно возвращать код 422\n* При именовании полей в форме (аттрибут *name*) используйте схему, которая описана и показана в теории. Все данные формы должны попадать в один массив, именем которого является имя сущности.\n","has_solution":true,"entity_name":"Модифицирующие формы"},"units":[{"id":2430,"name":"theory","url":"/courses/php-mvc/lessons/post-form/theory_unit"},{"id":2655,"name":"quiz","url":"/courses/php-mvc/lessons/post-form/quiz_unit"},{"id":2453,"name":"exercise","url":"/courses/php-mvc/lessons/post-form/exercise_unit"}],"links":[{"id":428364,"name":"Laravel Form builder","url":"https://github.com/kristijanhusak/laravel-form-builder"},{"id":428365,"name":"Null-коалесцентный оператор","url":"https://php.net/manual/ru/migration70.new-features.php#migration70.new-features.null-coalesce-op"}],"ordered_units":[{"id":2430,"name":"theory","url":"/courses/php-mvc/lessons/post-form/theory_unit"},{"id":2655,"name":"quiz","url":"/courses/php-mvc/lessons/post-form/quiz_unit"},{"id":2453,"name":"exercise","url":"/courses/php-mvc/lessons/post-form/exercise_unit"}],"id":1166,"slug":"post-form","state":"approved","name":"Модифицирующие формы","course_order":382,"goal":"Учимся изменять данные на сервере с помощью форм","self_study":"1. Создайте шаблон и добавьте обработчик, который выводит форму создания пользователя с полями: nickname и email. А вот id должен генерироваться внутри приложения\n\n \n2. Добавьте обработчик, который сохраняет введенные данные. Для хранения пользователя используйте файл. Сами данные можно кодировать в JSON с помощью `json_encode()` и декодировать с помощью `json_decode()`. Для работы с файлом используйте функции [file_put_contents()](https://www.php.net/manual/ru/function.file-put-contents.php) и [file_get_contents()](https://www.php.net/manual/ru/function.file-get-contents.php)\n3. После добавления данных в файл должен происходить редирект на адрес **/users**\n","theory_video_provider":null,"theory_video_uid":null,"theory":"Формы, которые изменяют данные, устроены сложнее как с клиентской стороны, так и с серверной. Для уверенной работы с ними необходимо разбираться в следующих вопросах:\n\n* Знание соответствующих HTML-тегов\n* Понимание того, как отправляются формы по HTTP\n* Обработка на стороне сервера\n* Валидация и вывод ошибок\n\nНачнем с того, что за вывод формы и ее обработку должны отвечать два разных обработчика, значит, это разные маршруты. Ниже пример маршрутов для создания нового пользователя:\n\n* GET */users/new* — страница с формой, которую заполняет пользователь. Эта форма отправляет POST-запрос на адрес */users*, указанный в атрибуте `action`\n* POST */users* — маршрут, обрабатывающий данные формы\n\nПодобная схема именования [рекомендуется](https://guides.rubyonrails.org/routing.html#resource-routing-the-rails-default) и автоматически создается многими фреймворками, такими как Rails. Она хорошо ложится на REST-архитектуру, о которой мы еще поговорим.\n\n## Форма\n\n```html\n<!-- templates/users/new.phtml -->\n<form action=\"/users\" method=\"post\">\n <div>\n <label>\n Имя\n <input type=\"text\" name=\"user[name]\">\n </label>\n </div>\n <div>\n <label>\n Email\n <input type=\"email\" required name=\"user[email]\">\n </label>\n </div>\n <div>\n <label>\n Пароль\n <input type=\"password\" required name=\"user[password]\">\n </label>\n </div>\n <div>\n <label>\n Подтверждение пароля\n <input type=\"password\" required name=\"user[passwordConfirmation]\">\n </label>\n </div>\n <div>\n <label>\n Город\n <select name=\"user[city]\">\n <option value=\"3\">Москва</option>\n <option value=\"13\">Пенза</option>\n <option value=\"399\">Томск</option>\n </select>\n </label>\n </div>\n <input type=\"submit\" value=\"Sign Up\">\n</form>\n```\n\nКаждое имя определяется как ключ в массиве `user`. Такой способ определения имен — необязательный, но он удобен для массовой обработки значений формы. Их изоляция в одном массиве позволяет избежать потенциальных пересечений с другими данными. В поисковых формах эта схема тоже удобна, если количество элементов больше одного.\n\nС точки зрения HTTP не существует способа передавать массивы. Если не указано иного, то данные формы кодируются в теле запроса как *application/x-www-form-urlencoded*. Чисто технически это выглядит как строка запроса с парами ключ-значение, объединенные символом *&*:\n\n POST /users HTTP/1.1\n Host: example.com\n Content-type: application/x-www-form-urlencoded\n Content-length: 42\n\n key=value&key2=value2&user%5Bname%5D%3Djohn\n\nВ конце тела закодирован ключ `user[name]`. Превращение таких ключей в массив идет на уровне интерпретатора в случае PHP. Либо на уровне самого фреймворка в случае остальных языков.\n\n## Обработка данных\n\n```php\n<?php\n\n$repo = new App\\UserRepository();\n\n$app->post('/users', function ($request, $response) use ($repo) {\n $validator = new Validator();\n $user = $request->getParsedBodyParam('user');\n $errors = $validator->validate($user);\n if (count($errors) === 0) {\n $repo->save($user);\n return $response->withRedirect('/users', 302);\n }\n $params = [\n 'user' => $user,\n 'errors' => $errors\n ];\n return $this->get('renderer')->render($response, \"users/new.phtml\", $params);\n});\n```\n\nОбработка данных формы начинается с извлечения данных из тела запроса. Это можно сделать двумя способами, похожими на то, как мы извлекаем параметры запроса:\n\n* `getParsedBody()` — извлекает все данные\n* `getParsedBodyParam($name, $defaultValue)` — извлекает значение конкретного параметра. Вторым параметром принимает значение по умолчанию\n\n```php\n<?php\n\n$user = $request->getParsedBodyParam('user');\n```\n\nДалее нужно убедиться в том, что данные введены верно. Этот процесс называется **валидацией**.\n\nSlim не предоставляет механизмов для валидации. Ее можно получить из сторонних библиотек. В нашем случае валидация реализуется классом с одним методом `validate()`, который проверяет данные формы. Также он возвращает специальный массив `$errors`. В нем ключ — это название поля, а значение — текст ошибки, который нужно вывести в форме:\n\n```php\n<?php\n\n$validator = new Validator();\n\n// function validate($user)\n// {\n// $errors = [];\n// if (empty($user['name'])) {\n// $errors['name'] = \"Can't be blank\"\n// }\n//\n// // ...\n//\n// return $errors;\n// }\n$errors = $validator->validate($user);\n```\n\nЕсли ошибок нет, то данные формы сохраняются, например, в базу данных. Об этом подробнее в следующем уроке. После сохранения выполняется перенаправление (HTTP redirect). За перенаправление отвечает заголовок *Location* и статусы с кодом 3XX:\n\n```php\n<?php\n\nif (count($errors) === 0) {\n $repo->save($user);\n return $response->withRedirect('/users');\n}\n```\n\nЕсли в процессе обработки возникли ошибки, выполняется рендеринг формы из шаблона, который мы использовали для */users/new*. В этот шаблон передаются как данные формы, так и список ошибок.\n\nРедиректа не происходит, в адресной строке остается адрес */users*. Если попробовать в этот момент нажать <kbd>f5</kbd>, то браузер выдаст предупреждение о том, что мы пытаемся повторно отправить данные. Это сообщение предупреждает о том, что метод POST не идемпотентен, и повторная отправка формы может привести к повторному созданию пользователя:\n\n```php\n<?php\n\n$params = [\n 'user' => $user,\n 'errors' => $errors\n];\nreturn $this->get('renderer')->render($response, \"users/new.phtml\", $params);\n```\n\nВернемся к нашей форме и изменим ее так, чтобы в нее подставлялись возникающие ошибки и значения полей, введенные пользователем:\n\n```html\n<!-- templates/users/new.phtml -->\n<form action=\"/users\" method=\"post\">\n <div>\n <label>\n Имя\n <input type=\"text\" name=\"user[name]\" value=\"<?= htmlspecialchars($user['name']) ?>\">\n </label>\n <?php if (isset($errors['name'])): ?>\n <div><?= $errors['name'] ?></div>\n <?php endif ?>\n </div>\n <div>\n <label>\n Email\n <input type=\"email\" required name=\"user[email]\" value=\"<?= htmlspecialchars($user['email']) ?>\">\n </label>\n <?php if (isset($errors['email'])): ?>\n <div><?= $errors['email'] ?></div>\n <?php endif ?>\n </div>\n <div>\n <label>\n Пароль\n <input type=\"password\" required name=\"user[password]\" value=\"<?= htmlspecialchars($user['password']) ?>\">\n </label>\n <?php if (isset($errors['password'])): ?>\n <div><?= $errors['password'] ?></div>\n <?php endif ?>\n </div>\n <div>\n <label>\n Подтверждение пароля\n <input type=\"password\" required name=\"user[passwordConfirmation]\" value=\"<?= htmlspecialchars($user['passwordConfirmation']) ?>\">\n </label>\n </div>\n <div>\n <label>\n Город\n <select name=\"user[city]\">\n <option value=\"\">Select</option>\n <option <?= $user['city'] === '3' ? 'selected' : '' ?> value=\"3\">Москва</option>\n <option <?= $user['city'] === '13' ? 'selected' : '' ?> value=\"13\">Пенза</option>\n <option <?= $user['city'] === '399' ? 'selected' : '' ?> value=\"399\">Томск</option>\n </select>\n </label>\n <?php if (isset($errors['city'])): ?>\n <div><?= $errors['city'] ?></div>\n <?php endif ?>\n </div>\n <input type=\"submit\" value=\"Sign Up\">\n</form>\n```\n\nТакое изменение формы требует изменения обработчика */users/new*. Чтобы избежать ошибок, нужно передать в шаблон пустой массив `$errors` и массив `$user`. В последнем необходимо задать значения по умолчанию для соответствующих полей формы. Так в шаблоне не придется выполнять проверку данных формы на существование:\n\n```php\n<?php\n\n$app->get('/users/new', function ($request, $response) {\n $params = [\n 'user' => ['name' => '', 'email' => '', 'password' => '', 'passwordConfirmation' => '', 'city' => ''],\n 'errors' => []\n ];\n return $this->get('renderer')->render($response, \"users/new.phtml\", $params);\n});\n```\n\nФорма увеличилась. На практике она будет еще больше из-за дополнительного оформления, например, отступов и подсветки ошибок. С опытом станет понятно, что так невозможно работать. Ради простейшей обработки придется писать много идентичного кода в HTML. Эту работу нужно автоматизировать.\n\nДля генерации форм используются специальные билдеры. Микрофреймворки не имеют встроенных билдеров, поэтому придется искать их самостоятельно.\n\nДовольно популярны [формы](https://symfony.com/doc/current/components/form.html) из фреймворка Symfony. В этом компоненте каждая форма представлена своим классом. Компонент поддерживает валидацию, имеет встроенные механизмы защиты от некоторых атак и многое другое.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":2433,"name":"theory","url":"/courses/php-mvc/lessons/about/theory_unit"}],"links":[{"id":428346,"name":"Что такое протокол HTTPS, и как он защищает вас в интернете","url":"https://guides.hexlet.io/ru/https-yandex-guide/"},{"id":428347,"name":"Как работает DNS","url":"https://guides.hexlet.io/ru/dns/"}],"ordered_units":[{"id":2433,"name":"theory","url":"/courses/php-mvc/lessons/about/theory_unit"}],"id":1169,"slug":"about","state":"approved","name":"Введение","course_order":1,"goal":"Узнаем о курсе, его структуре, задачах и целях","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Чтобы перейти от написания скриптов к созданию полноценных сайтов, нужно изучить много новых понятий и инструментов, которые не относятся к языку программирования. Например, нужно знать, как работают операционные системы, как работать с сетями, регистраторами, хостингом и деплоем сайта.\n\nНа собеседованиях веб-программистам часто задают вопрос «Что происходит после того, как в адресной строке браузера набирается сайт www.google.com и нажимается Enter?». Обычно на этот вопрос хотят услышать ключевые понятия, связанные с веб-разработкой:\n\n* Выполнение DNS-запроса для получения IP-адреса домена\n* Соединение с веб-сервером, находящемся по этому адресу на порту 443 (или 80) по TCP\n* Выполнение HTTP-запроса для получения содержимого сайта по указанному домену\n* Получение ответа и рендеринг содержимого во вкладке браузера.\n\nКаждый из этих пунктов подразумевает знание следующих тем:\n\n* Протокол HTTP. Понятие виртуальных хостов. Желательно понимание принципов работы HTTPS\n* Принципы работы DNS\n* Знание TCP/IP. Понятия: порт, маска, подсети. Модель OSI. Сетевые сокеты\n* Веб-сервер. Что это такое, как работает и зачем нужен\n\nПодробный ответ на этот вопрос доступен [здесь](https://github.com/alex/what-happens-when).\n\nТакже на Хекслете есть ответы на некоторые из перечисленных пунктов, но большую часть материала придется взять из сторонних источников. Большинство ответов на указанные темы можно получить в книгах по операционным системам. В наших [рекомендованных книгах](https://ru.hexlet.io/pages/recommended-books) есть все необходимое.\n\nЗнание HTTP можно взять из соответствующего курса. Общее понимание DNS, хостинга, деплоя — из курса [Введение в веб-разработку](https://ru.hexlet.io/courses/intro_to_web_development). Остальное есть в дополнительных ссылках.\n\nТакже рекомендуем посмотреть [публичное собеседование](https://www.youtube.com/watch?v=JERUf-xKU1o&index=6&list=PLo6puixMwuSOa_0EH6X4OXzFAmyQGS3a3), где поднимались эти вопросы.\n\nВ процессе разработки сайта нужно изучать фреймворки, микрофреймворки, роутинг, куки, сессии, безопасность, шаблонизацию и взаимодействие с базой данных. И даже после того, как вы научитесь создавать сайты, обучение нужно продолжать.\n\nЧтобы сайт был доступен пользователям, его нужно развернуть на удаленном сервере. Этот процесс называется «деплой» — процесс разворачивания сайта на хостинге. При этом существует разный тип хостинга: IaaS (AWS), PaaS (Render), Shared Hosting (виртуальный хостинг), VPS/VDS. Также перед этим нужно настроить удаленную машину с помощью инструмента, например, Ansible, что тоже стоит изучить.\n\nДанный курс посвящен разработке сайтов с использованием микрофреймворков. Темы, указанные выше, также включены в курс, но разбираются поверхностно. Поэтому рекомендуем выполнять задачи не только в среде Хекслета, но и локально, параллельно выкатывая код на сервис, подобный [Render](https://www.render.com/).\n"},"id":165,"slug":"php-mvc","challenges_count":3,"name":"Веб-разработка на PHP","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы познакомитесь с веб-разработкой на PHP. Вы узнаете о MVC, шаблонизации, роутинге и отправке форм. В итоге поймете, как создаются элементы типичных сайтов, например, аутентификация или полный набор операций по работе с сущностью (CRUD). Знания из этого курса пригодятся, если вы решите написать свой сайт на микрофреймворке Slim.","kind":"basic","updated_at":"2026-02-20T10:25:58.282Z","language":"php","duration_cache":95100,"skills":["Создавать с помощью PHP полноценные сайты","Пользоваться встроенным в PHP веб-сервером","Работать с микрофреймворком Slim","Правильно строить архитектуру веб-приложений. Разбираться в MVC"],"keywords":["CGI","slim framework","шаблонизация","отправка форм","MVC","сессии","роутинг"],"lessons_count":28,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MTUyODgsInB1ciI6ImJsb2JfaWQifX0=--ba054c4897951eb2e6cea29301c46f15adb3aeb2/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":2,"slug":"php","title":"PHP-разработчик","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":60,"duration_in_months":10},"id":1,"slug":"php","title":"РНР-разработчик","subtitle":"Изучите PHP и Laravel для разработки и проектирования REST API","subtitle_for_lists":"Изучите PHP и Laravel для разработки и проектирования REST API","locale":"ru","current":true,"duration_in_months_text":"10 месяцев","stack_slug":"php","price_text":"от 5 650 ₽","duration_text":"10 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk5MiwicHVyIjoiYmxvYl9pZCJ9fQ==--e9d0f30948ea766a7e6bc3e3d56c192344d45fb8/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programming-cuate%20(1).png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/php-mvc/lessons/post-form/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Веб-разработка на PHP</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Модифицирующие формы</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Модифицирующие формы","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Веб-разработка на PHP"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Формы, которые изменяют данные, устроены сложнее как с клиентской стороны, так и с серверной. Для уверенной работы с ними необходимо разбираться в следующих вопросах:</p>
<ul>
<li>Знание соответствующих HTML-тегов</li>
<li>Понимание того, как отправляются формы по HTTP</li>
<li>Обработка на стороне сервера</li>
<li>Валидация и вывод ошибок</li>
</ul>
<p>Начнем с того, что за вывод формы и ее обработку должны отвечать два разных обработчика, значит, это разные маршруты. Ниже пример маршрутов для создания нового пользователя:</p>
<ul>
<li>GET <em>/users/new</em> — страница с формой, которую заполняет пользователь. Эта форма отправляет POST-запрос на адрес <em>/users</em>, указанный в атрибуте <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">action</code></li>
<li>POST <em>/users</em> — маршрут, обрабатывающий данные формы</li>
</ul>
<p>Подобная схема именования <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://guides.rubyonrails.org/routing.html#resource-routing-the-rails-default" rel="noopener noreferrer" target="_blank">рекомендуется</a> и автоматически создается многими фреймворками, такими как Rails. Она хорошо ложится на REST-архитектуру, о которой мы еще поговорим.</p>
<h2 id="heading-2-1">Форма</h2>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><!-- templates/users/new.phtml -->
<form action="/users" method="post">
<div>
<label>
Имя
<input type="text" name="user[name]">
</label>
</div>
<div>
<label>
Email
<input type="email" required name="user[email]">
</label>
</div>
<div>
<label>
Пароль
<input type="password" required name="user[password]">
</label>
</div>
<div>
<label>
Подтверждение пароля
<input type="password" required name="user[passwordConfirmation]">
</label>
</div>
<div>
<label>
Город
<select name="user[city]">
<option value="3">Москва</option>
<option value="13">Пенза</option>
<option value="399">Томск</option>
</select>
</label>
</div>
<input type="submit" value="Sign Up">
</form></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Каждое имя определяется как ключ в массиве <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">user</code>. Такой способ определения имен — необязательный, но он удобен для массовой обработки значений формы. Их изоляция в одном массиве позволяет избежать потенциальных пересечений с другими данными. В поисковых формах эта схема тоже удобна, если количество элементов больше одного.</p>
<p>С точки зрения HTTP не существует способа передавать массивы. Если не указано иного, то данные формы кодируются в теле запроса как <em>application/x-www-form-urlencoded</em>. Чисто технически это выглядит как строка запроса с парами ключ-значение, объединенные символом <em>&</em>:</p>
<code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">POST /users HTTP/1.1
Host: example.com
Content-type: application/x-www-form-urlencoded
Content-length: 42
key=value&key2=value2&user%5Bname%5D%3Djohn</code>
<p>В конце тела закодирован ключ <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">user[name]</code>. Превращение таких ключей в массив идет на уровне интерпретатора в случае PHP. Либо на уровне самого фреймворка в случае остальных языков.</p>
<h2 id="heading-2-2">Обработка данных</h2>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
$repo = new App\UserRepository();
$app->post('/users', function ($request, $response) use ($repo) {
$validator = new Validator();
$user = $request->getParsedBodyParam('user');
$errors = $validator->validate($user);
if (count($errors) === 0) {
$repo->save($user);
return $response->withRedirect('/users', 302);
}
$params = [
'user' => $user,
'errors' => $errors
];
return $this->get('renderer')->render($response, "users/new.phtml", $params);
});</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Обработка данных формы начинается с извлечения данных из тела запроса. Это можно сделать двумя способами, похожими на то, как мы извлекаем параметры запроса:</p>
<ul>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">getParsedBody()</code> — извлекает все данные</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">getParsedBodyParam($name, $defaultValue)</code> — извлекает значение конкретного параметра. Вторым параметром принимает значение по умолчанию</li>
</ul>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
$user = $request->getParsedBodyParam('user');</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Далее нужно убедиться в том, что данные введены верно. Этот процесс называется <strong>валидацией</strong>.</p>
<p>Slim не предоставляет механизмов для валидации. Ее можно получить из сторонних библиотек. В нашем случае валидация реализуется классом с одним методом <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">validate()</code>, который проверяет данные формы. Также он возвращает специальный массив <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">$errors</code>. В нем ключ — это название поля, а значение — текст ошибки, который нужно вывести в форме:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
$validator = new Validator();
// function validate($user)
// {
// $errors = [];
// if (empty($user['name'])) {
// $errors['name'] = "Can't be blank"
// }
//
// // ...
//
// return $errors;
// }
$errors = $validator->validate($user);</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если ошибок нет, то данные формы сохраняются, например, в базу данных. Об этом подробнее в следующем уроке. После сохранения выполняется перенаправление (HTTP redirect). За перенаправление отвечает заголовок <em>Location</em> и статусы с кодом 3XX:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
if (count($errors) === 0) {
$repo->save($user);
return $response->withRedirect('/users');
}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если в процессе обработки возникли ошибки, выполняется рендеринг формы из шаблона, который мы использовали для <em>/users/new</em>. В этот шаблон передаются как данные формы, так и список ошибок.</p>
<p>Редиректа не происходит, в адресной строке остается адрес <em>/users</em>. Если попробовать в этот момент нажать <kbd>f5</kbd>, то браузер выдаст предупреждение о том, что мы пытаемся повторно отправить данные. Это сообщение предупреждает о том, что метод POST не идемпотентен, и повторная отправка формы может привести к повторному созданию пользователя:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
$params = [
'user' => $user,
'errors' => $errors
];
return $this->get('renderer')->render($response, "users/new.phtml", $params);</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Вернемся к нашей форме и изменим ее так, чтобы в нее подставлялись возникающие ошибки и значения полей, введенные пользователем:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><!-- templates/users/new.phtml -->
<form action="/users" method="post">
<div>
<label>
Имя
<input type="text" name="user[name]" value="<?= htmlspecialchars($user['name']) ?>">
</label>
<?php if (isset($errors['name'])): ?>
<div><?= $errors['name'] ?></div>
<?php endif ?>
</div>
<div>
<label>
Email
<input type="email" required name="user[email]" value="<?= htmlspecialchars($user['email']) ?>">
</label>
<?php if (isset($errors['email'])): ?>
<div><?= $errors['email'] ?></div>
<?php endif ?>
</div>
<div>
<label>
Пароль
<input type="password" required name="user[password]" value="<?= htmlspecialchars($user['password']) ?>">
</label>
<?php if (isset($errors['password'])): ?>
<div><?= $errors['password'] ?></div>
<?php endif ?>
</div>
<div>
<label>
Подтверждение пароля
<input type="password" required name="user[passwordConfirmation]" value="<?= htmlspecialchars($user['passwordConfirmation']) ?>">
</label>
</div>
<div>
<label>
Город
<select name="user[city]">
<option value="">Select</option>
<option <?= $user['city'] === '3' ? 'selected' : '' ?> value="3">Москва</option>
<option <?= $user['city'] === '13' ? 'selected' : '' ?> value="13">Пенза</option>
<option <?= $user['city'] === '399' ? 'selected' : '' ?> value="399">Томск</option>
</select>
</label>
<?php if (isset($errors['city'])): ?>
<div><?= $errors['city'] ?></div>
<?php endif ?>
</div>
<input type="submit" value="Sign Up">
</form></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Такое изменение формы требует изменения обработчика <em>/users/new</em>. Чтобы избежать ошибок, нужно передать в шаблон пустой массив <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">$errors</code> и массив <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">$user</code>. В последнем необходимо задать значения по умолчанию для соответствующих полей формы. Так в шаблоне не придется выполнять проверку данных формы на существование:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><?php
$app->get('/users/new', function ($request, $response) {
$params = [
'user' => ['name' => '', 'email' => '', 'password' => '', 'passwordConfirmation' => '', 'city' => ''],
'errors' => []
];
return $this->get('renderer')->render($response, "users/new.phtml", $params);
});</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Форма увеличилась. На практике она будет еще больше из-за дополнительного оформления, например, отступов и подсветки ошибок. С опытом станет понятно, что так невозможно работать. Ради простейшей обработки придется писать много идентичного кода в HTML. Эту работу нужно автоматизировать.</p>
<p>Для генерации форм используются специальные билдеры. Микрофреймворки не имеют встроенных билдеров, поэтому придется искать их самостоятельно.</p>
<p>Довольно популярны <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://symfony.com/doc/current/components/form.html" rel="noopener noreferrer" target="_blank">формы</a> из фреймворка Symfony. В этом компоненте каждая форма представлена своим классом. Компонент поддерживает валидацию, имеет встроенные механизмы защиты от некоторых атак и многое другое.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/php?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">10 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">РНР-разработчик</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите PHP и Laravel для разработки и проектирования REST API</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzk5MiwicHVyIjoiYmxvYl9pZCJ9fQ==--e9d0f30948ea766a7e6bc3e3d56c192344d45fb8/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Programming-cuate%20(1).png" alt="РНР-разработчик" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 5 650 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/php-mvc/lessons/post-form/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 28</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/php-mvc/lessons/post-form/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>