В прошлой части мы разобрали, как данные превращаются в HTML: view/Blade в Laravel против страницы+компонента+шаблона в Битриксе.
Теперь логичный шаг — “обратное направление”: как UI принимает данные от пользователя и как вы не превращаете проект в набор if (empty($_POST['NAME'])).
Формы — это не просто <form>...</form>. Это одновременно:
- входная точка безопасности (CSRF, XSS, загрузки файлов),
- граница домена (какие данные мы вообще принимаем и в каком формате),
- UX‑контракт (какие ошибки показываем, где и когда),
- и часто — точка боли в legacy (валидация размазана по шаблонам/компонентам/страницам).
В этой части разберём:
- как правильно строить поток “форма → обработка → ошибки → повторный ввод”,
- как Laravel делает валидацию “как слой” через Form Request,
- как это обычно решают в Битриксе через компоненты / контроллеры / Result+Errors,
- и где лучше делать валидацию: на входе, в ORM, в событиях, или везде понемногу.
Создание форм
Одна и та же задача в обоих стеках
Схема почти везде одинаковая:
- Отдать HTML формы (GET).
- Принять данные (POST).
- Провалидировать.
- Если ошибки — вернуть форму с ошибками и “старыми” данными.
- Если всё ок — выполнить действие (создать запись/отправить письмо) и показать успех.
Разница в том, насколько стандартно это делается.
Laravel: Blade‑форма, CSRF, old input и ошибки
HTML‑форма (Blade)
Типовой пример формы “обратной связи”:
<form method="POST" action="{{ route('feedback.store') }}" enctype="multipart/form-data">
@csrf
<label>
Имя
<input name="name" value="{{ old('name') }}">
@error('name')
<div class="error">{{ $message }}</div>
@enderror
</label>
<label>
Email
<input name="email" value="{{ old('email') }}">
@error('email')
<div class="error">{{ $message }}</div>
@enderror
</label>
<label>
Сообщение
<textarea name="message">{{ old('message') }}</textarea>
@error('message')
<div class="error">{{ $message }}</div>
@enderror
</label>
<label>
Файл (опционально)
<input type="file" name="attachment">
@error('attachment')
<div class="error">{{ $message }}</div>
@enderror
</label>
<button type="submit">Отправить</button>
</form>
Что здесь важно как “норма”:
@csrf— защита от CSRF. Это не “опция”, а обязательная привычка.old('field')— возвращает введённые данные после ошибки валидации.@error('field')— удобный вывод сообщений об ошибках (через error bag).
Практическая мысль: в Laravel форма — это часть стандартизированного потока, где “старые данные” и ошибки — не самодельная механика, а база.
Laravel: Form Request как контракт входных данных
В Laravel лучший “по‑умолчанию” путь — вынести валидацию в Form Request.
Идея: контроллер не должен знать “как проверять поля”, контроллер должен знать “что сделать с валидными данными”.
Пример:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
final class StoreFeedbackRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:100'],
'email' => ['required', 'string', 'email:rfc,dns', 'max:255'],
'message' => ['required', 'string', 'min:10', 'max:2000'],
'attachment' => ['nullable', 'file', 'max:5120'],
'source' => ['nullable', Rule::in(['site', 'landing', 'telegram'])],
];
}
public function messages(): array
{
return [
'message.min' => 'Опишите задачу чуть подробнее (минимум :min символов).',
'attachment.max' => 'Файл слишком большой (максимум :max КБ).',
];
}
public function attributes(): array
{
return [
'name' => 'имя',
'email' => 'email',
'message' => 'сообщение',
'attachment' => 'файл',
];
}
}
А если нужен UX‑тюнинг — добавляйте messages() (свои тексты) и attributes() (человеческие названия полей). Плюс, в Form Request удобно держать проверку доступа через authorize().
Контроллер становится предсказуемым:
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StoreFeedbackRequest;
use Illuminate\Http\RedirectResponse;
final class FeedbackController extends Controller
{
public function store(StoreFeedbackRequest $request): RedirectResponse
{
$data = $request->validated();
// Здесь: сохранение / письмо / очередь — но НЕ проверка полей.
return back()->with('status', 'Спасибо! Мы получили сообщение.');
}
}
“Хуки” Form Request: нормализация и кросс‑валидация
Если вам нужно “подчистить” вход (trim/приведение типов) — используйте prepareForValidation(). Если нужно проверять правила “между полями” — withValidator()/after() (например, “или email, или телефон обязателен”).
Пример нормализации:
protected function prepareForValidation(): void
{
$this->merge([
'email' => is_string($this->input('email')) ? trim($this->input('email')) : null,
]);
}
Пример кросс‑валидации:
public function withValidator($validator): void
{
$validator->after(function ($validator) {
$source = (string)$this->input('source', '');
$message = (string)$this->input('message', '');
if ($source === 'landing' && mb_strlen(trim($message)) < 50) {
$validator->errors()->add('message', 'Для заявки с лендинга опишите задачу чуть подробнее (минимум 50 символов).');
}
});
}
Bitrix: форма через свой компонент (каноничный “сайтовый” путь)
Базовая идея компонента (без деталей проекта): принять POST, проверить сессию, собрать данные, провалидировать через Result, и отрендерить форму повторно с ошибками.
<?php
use Bitrix\Main\Context;
use Bitrix\Main\Result;
final class FeedbackFormComponent extends CBitrixComponent
{
public function executeComponent()
{
$request = Context::getCurrent()->getRequest();
$this->arResult['VALUES'] = [
'NAME' => (string)$request->getPost('NAME'),
'EMAIL' => (string)$request->getPost('EMAIL'),
'MESSAGE' => (string)$request->getPost('MESSAGE'),
];
$this->arResult['ERRORS'] = [];
$this->arResult['SUCCESS'] = false;
if ($request->isPost() && check_bitrix_sessid()) {
$result = $this->validate($this->arResult['VALUES']);
if ($result->isSuccess()) {
// TODO: сохранить/отправить
$this->arResult['SUCCESS'] = true;
$this->arResult['VALUES'] = ['NAME' => '', 'EMAIL' => '', 'MESSAGE' => ''];
} else {
$this->arResult['ERRORS'] = $result->getErrorMessages();
}
}
$this->includeComponentTemplate();
}
private function validate(array $values): Result
{
$result = new Result();
if (trim($values['NAME']) === '') {
$result->addError(new \Bitrix\Main\Error('Укажите имя.'));
}
if (trim($values['EMAIL']) === '' || !check_email($values['EMAIL'])) {
$result->addError(new \Bitrix\Main\Error('Укажите корректный email.'));
}
if (mb_strlen(trim($values['MESSAGE'])) < 10) {
$result->addError(new \Bitrix\Main\Error('Сообщение слишком короткое.'));
}
return $result;
}
}
В template.php обычно важно не забыть два “барьера”:
<form method="POST" action="<?= POST_FORM_ACTION_URI ?>">
<?= bitrix_sessid_post() ?>
<!-- inputs + вывод ошибок -->
</form>
Если вы делаете формы на “сайте Битрикса”, компонент — хороший “контейнер” для полного потока: вход → проверка → результат → шаблон.
Bitrix: формы для AJAX/API через D7‑контроллеры и Action Filters
Если форма работает как AJAX (или вы делаете SPA/внешнюю интеграцию), зачастую чище вынести обработку в D7‑контроллер:
- строгий JSON‑ответ,
- единая точка prefilters (CSRF/метод/авторизация),
- предсказуемая обработка ошибок.
Не так давно в BitrixFramework появился штатный механизм валидации на основе PHP‑атрибутов (DTO + правила), который отлично подходит именно для контроллеров и API.
Схематично (через автоподстановку валидируемого DTO):
<?php
use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Engine\ActionFilter;
use Bitrix\Main\Validation\Engine\AutoWire\ValidationParameter;
use Bitrix\Main\Validation\Rule\Email;
use Bitrix\Main\Validation\Rule\Length;
use Bitrix\Main\Validation\Rule\NotEmpty;
final class FeedbackDto
{
public function __construct(
#[NotEmpty]
#[Length(max: 100)]
public ?string $name = null,
#[NotEmpty]
#[Email]
#[Length(max: 255)]
public ?string $email = null,
#[NotEmpty]
#[Length(min: 10, max: 2000)]
public ?string $message = null,
) {}
public static function createFromRequest(\Bitrix\Main\HttpRequest $request): self
{
return new self(
name: (string)$request->get('name'),
email: (string)$request->get('email'),
message: (string)$request->get('message'),
);
}
}
final class FeedbackController extends Controller
{
public function getAutoWiredParameters()
{
return [
new ValidationParameter(
FeedbackDto::class,
fn () => FeedbackDto::createFromRequest($this->getRequest()),
),
];
}
public function configureActions(): array
{
return [
'send' => [
'prefilters' => [
new ActionFilter\HttpMethod([ActionFilter\HttpMethod::METHOD_POST]),
new ActionFilter\Csrf(),
],
],
];
}
public function sendAction(FeedbackDto $dto): array
{
// TODO: сохранить/отправить
return ['ok' => true];
}
}
Если FeedbackDto невалиден, sendAction не выполнится — контроллер вернёт JSON‑ошибки автоматически (это как раз то, чего обычно не хватало для API‑стиля).
Валидация: один принцип для обоих стеков
Принцип, который спасает проекты
Валидация должна отвечать на два вопроса:
- Что мы принимаем? (формат, типы, длины, диапазоны)
- Имеем ли мы право это сделать? (контекст, авторизация, бизнес‑правила)
И второй пункт часто важнее: “валидные данные” не означают, что действие разрешено.
Как это обычно раскладывается по слоям:
- Laravel: формат и ограничения — в
rules(), “можно ли вообще выполнять действие” — вauthorize()(и/или policy/gate), а контроллер работает уже с$request->validated(). - Битрикс: проверка формата/обязательности — в одном месте (компонент/контроллер/валидатор), а права/контекст — через prefilters (в контроллерах) или явные проверки до выполнения действия (в компоненте/сервисе).
Laravel: Validation Rules, кастомные правила и сообщения
Базовые правила и “читабельная строгость”
Если у вас уже есть Form Request, этот блок — про то, чем обычно “докручивают” правила в реальных проектах:
bail— остановиться на первой ошибке правила (уменьшает “шум”).Rule::in(...)— белый список значений (“type/status/source”).Rule::unique(...)+ignore(...)— корректная уникальность на update.
Кастомное правило (валидатор “смыслом”, а не regex’ом)
Если логика сложнее, лучше сделать правило, а не писать 3 regex’а и 5 if.
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
final class NoLinks implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$text = is_string($value) ? $value : '';
if (preg_match('~https?://~i', $text) || str_contains(mb_strtolower($text), 'www.')) {
$fail('Ссылки в сообщении запрещены.');
}
}
}
Использование:
'message' => ['required', 'string', 'min:10', 'max:2000', new \App\Rules\NoLinks()],
Сообщения об ошибках и локализация
В Laravel, помимо точечных сообщений в Form Request, есть системный слой локализации — языковые файлы (lang/ru/validation.php), что удобно, когда вы хотите единый стиль ошибок по всему проекту.
Практическая мысль: тексты ошибок — часть UX. Если вы делаете продукт, это не “лишнее”, это то, что пользователь реально читает.
Bitrix: как не размазывать проверки — Result/ErrorCollection как контракт
В Bitrix форма легко “расползается” на проверки в компоненте, в шаблоне и ещё где‑то. Чтобы этого не произошло, удобнее зафиксировать простой контракт: валидация возвращает Result с ошибками, а UI‑слой только показывает их.
Это помогает держать правила в одном месте и переиспользовать их между разными входами (форма на сайте / AJAX / импорт).
Дальше у вас обычно два “здоровых” варианта (и их можно сочетать):
- Компонентная форма (сайт): держите правила в отдельном валидаторе, который возвращает
Result(как в примере выше сvalidate()), а компонент только прокидываетVALUES/ERRORSв шаблон. - AJAX/API (D7‑контроллеры): используйте DTO + PHP‑атрибуты и автопроверку параметров. Это даёт предсказуемые JSON‑ошибки без ручного “собирания” ответа.
Bitrix: валидация на уровне ORM (когда это оправдано)
В Bitrix D7 ORM у полей сущности есть валидаторы (например, длина или формат). Это полезно, когда:
- данные могут попадать в таблицу не только из “формы на сайте” (например, импорт/интеграция),
- и вы хотите дополнительный “предохранитель” на уровне слоя данных.
Упрощённая идея — валидаторы прямо в карте полей:
use Bitrix\Main\ORM\Fields\StringField;
use Bitrix\Main\ORM\Fields\Validators\LengthValidator;
use Bitrix\Main\ORM\Fields\Validators\RegExpValidator;
(new StringField('EMAIL'))
->configureRequired(true)
->addValidator(new LengthValidator(3, 255))
->addValidator(new RegExpValidator('~^.+@.+\..+$~'));
Если ORM‑операция не проходит, вы обычно получите ошибки в AddResult/UpdateResult, и их можно читать через getErrorMessages() / getErrors() / getErrorCollection().
Практическая мысль: ORM‑валидация — это “нижний уровень защиты”. Пользовательские ошибки и UX‑тексты чаще всё равно лучше держать в “валидации на входе”, чтобы контролировать сообщение, контекст и поля формы.
Где чаще всего ломаются формы (в обоих стеках)
Кейс A. Валидация “после действия”
Классика: сначала сохранили, потом поняли, что email плохой.
Решение одинаковое: валидировать до записи, а лучше — на уровне входного слоя (request/контроллер/компонент).
Кейс B. Ошибки без привязки к полям
Пользователь получает “что-то не так”, но не понимает где.
В Laravel это решается ошибками на конкретные поля (@error('field')).
В Bitrix часто нужно договориться о структуре ошибок (например, код ошибки = имя поля) и рендерить их рядом с инпутом.
Кейс C. CSRF “забыли один раз — и всё”
- Laravel:
@csrfв формах. - Bitrix:
bitrix_sessid_post()+check_bitrix_sessid()на обработке.
Кейс D. Загрузки файлов
Валидация файла — это не только “размер”. В нормальном проекте вы почти всегда хотите проверить:
- тип/формат,
- ограничения на публичный доступ,
- и место хранения (чтобы “пользовательский файл” не оказался в зоне исполнения).
Опыт: любую загрузку файлов оборачивайте строгой валидацией, независимо от стека.
Сравнительная таблица: формы и валидация
Шкала (как и раньше): от -2 до +2.
| Критерий | Laravel (баллы) | Битрикс (баллы) | Комментарий |
|---|---|---|---|
| Готовый “поток формы” (old input + вывод ошибок) | +2 | 0 | В Laravel это базовая механика. В Битриксе чаще делается руками/соглашениями. |
| Валидация как отдельный слой (Form Request) | +2 | +2 | В BitrixFramework теперь есть штатная валидация DTO через атрибуты и ValidationService/ValidationParameter, что заметно приближает опыт к “request validation” в Laravel. |
| CSRF и базовая безопасность формы | +2 | +2 | В обоих стеках есть понятная механика, главное — дисциплина. |
| Кастомные правила и читаемость валидации | +2 | +1 | В Laravel это очень “нативно” (Rules + сообщения + локализация). В Bitrix обычно либо ручные проверки, либо собственный валидаторный слой. |
| Валидация на уровне данных (ORM) | +1 | +2 | В Bitrix у ORM валидаторы — часть карты сущности. В Laravel чаще делают валидацию на входе + ограничения БД (и это нормальная стратегия). |
| Единый стиль ошибок для UI и API | +1 | +1 | Laravel автоматически отдаёт JSON‑ошибки (и статус 422), если запрос “хочет JSON”. В BitrixFramework для контроллеров есть штатный JSON‑ответ с ошибками при автопроверке DTO (а вот для компонентных форм UI‑отображение ошибок всё равно остаётся задачей проекта). |
| Итого за статью | +10 | +8 | |
| Общий счёт (накопительный) | +57 | +30 | Счёт накапливается по мере выхода статей. |
Laravel хорош тем, что “правильный путь” встроен в каркас: Form Request, единый вывод ошибок, old input, понятный поток UI → validation → action.
Битрикс хорош тем, что вы можете построить очень практичный поток через компоненты (для сайта) и D7‑контроллеры (для AJAX/API), и собирать ошибки через Result/ErrorCollection. Но чтобы формы не деградировали, вам почти всегда нужны соглашения:
- где живёт валидация,
- как формируются ошибки,
- как рендерятся ошибки и сохраняются значения,
- и как вы не размазываете проверки по
template.php.
В следующей части перейдём к теме, где валидация и безопасность выходят на первый план: аутентификация и авторизация (Breeze/Jetstream/Fortify в Laravel vs пользователи и права в Битриксе).
одна форма, два подхода, одинаковый UX
Цель: сделать одну и ту же форму “обратной связи” так, чтобы:
- пользователь видит ошибки у конкретных полей,
- введённые значения сохраняются при ошибке,
- есть CSRF,
- есть кастомное правило (“нельзя вставлять ссылки”),
- и один и тот же код валидации можно использовать повторно.
Задание для Laravel
- Сделайте страницу
/feedbackс формой (Blade). - Добавьте
POST /feedbackи контроллерFeedbackController@store. - Создайте
StoreFeedbackRequestс правилами и кастомными сообщениями. - Добавьте кастомное правило
NoLinksдля поляmessage. - При успехе — редирект назад с flash‑сообщением “успех”, при ошибке — показать ошибки у полей и вернуть old input.
Опорные доки: Validation, Requests (Form Requests).
Задание для Битрикса
Вариант A (сайтовый, компонентный):
- Создайте свой компонент
vendor:feedback.form. - В компоненте реализуйте поток POST‑обработки:
check_bitrix_sessid()как обязательный барьер,- сбор входных данных,
- валидация через отдельный метод/класс, возвращающий
Result, - ошибки →
$arResult['ERRORS'], значения →$arResult['VALUES'].
- В
template.php:bitrix_sessid_post(),- вывод ошибок рядом с полями (минимум — списком сверху),
- экранирование через
htmlspecialcharsbx.
Вариант B (AJAX/API‑стайл):
- Сделайте D7‑контроллер с
sendAction. - Подключите action filters (POST + CSRF).
- Верните JSON со структурой
ok: true/falseи сообщениями ошибок (можно черезaddErrors). - На фронте (даже без фреймворка) отрисуйте ошибки у полей.
Цель упражнения — почувствовать: в Bitrix качество форм чаще зависит не от “фичи ядра”, а от того, внедрили ли вы стандарт обработки и ошибок в проекте.