Когда вы впервые сталкиваетесь с Yii2, ActiveForm кажется простым инструментом. Но потом приходит задача: форма с динамическими полями, вложенные модели, зависимые выпадающие списки. И тут начинается танцы с бубном. В этой статье я покажу, как не сойти с ума и сделать все красиво.

Почему ActiveForm — это не просто форма

ActiveForm в Yii2 — это не просто генератор HTML. Это мощный инструмент, который умеет работать с моделями, валидацией и AJAX. Но чтобы раскрыть его потенциал, нужно понимать несколько вещей.

Во-первых, каждая форма привязана к модели. Даже если у вас сложная структура, лучше разбить её на несколько моделей. Например, для заказа у вас может быть модель Order и модель OrderItem. ActiveForm позволяет работать с ними через вложенные формы.

Во-вторых, валидация. Yii2 поддерживает клиентскую и серверную валидацию. Но для сложных сценариев (например, проверка уникальности комбинации полей) лучше использовать серверную валидацию с AJAX.

Правило: Не пытайтесь втиснуть все в одну модель. Используйте отдельные модели для каждой сущности, а затем объединяйте их через формы.

Динамические поля: когда полей много, а заранее неизвестно сколько

Представьте форму, где пользователь может добавлять товары в заказ. Количество товаров не ограничено. Как это сделать?

Самый простой способ — использовать JavaScript для добавления новых полей. Но в Yii2 есть более элегантное решение: использование виджета yii\widgets\ActiveForm в сочетании с yii\base\DynamicModel или вложенными моделями.

Вот пример, как добавить динамические поля для нескольких email-адресов:

// В контроллере
$model = new DynamicModel(['emails' => []]);
$model->addRule(['emails'], 'email', ['allowName' => true]);

if (Yii::$app->request->isPost) {
    $model->load(Yii::$app->request->post());
    if ($model->validate()) {
        // Сохраняем
    }
}

return $this->render('form', ['model' => $model]);

В представлении:

<?php $form = ActiveForm::begin(); ?>
<div id="emails-container">
    <?php foreach ($model->emails as $index => $email): ?>
        <?= $form->field($model, "emails[$index]")->textInput() ?>
    <?php endforeach; ?>
</div>
<button type="button" id="add-email">Добавить email</button>
<?php ActiveForm::end(); ?>

И JavaScript для добавления новых полей:

$('#add-email').on('click', function() {
    var index = $('#emails-container .form-group').length;
    var field = '<div class="form-group">' +
        '<label class="control-label" for="dynamicmodel-emails-' + index + '">Email</label>' +
        '<input type="text" id="dynamicmodel-emails-' + index + '" class="form-control" name="DynamicModel[emails][' + index + ']">' +
        '</div>';
    $('#emails-container').append(field);
});

Вложенные формы: работаем с несколькими моделями

Часто форма содержит данные из разных таблиц. Например, форма регистрации пользователя включает данные профиля. В Yii2 это решается через вложенные модели.

Создайте две модели: User и Profile. В контроллере:

$user = new User();
$profile = new Profile();

if (Yii::$app->request->isPost) {
    $user->load(Yii::$app->request->post());
    $profile->load(Yii::$app->request->post());
    if ($user->validate() && $profile->validate()) {
        $user->save();
        $profile->user_id = $user->id;
        $profile->save();
        // Редирект
    }
}

return $this->render('register', ['user' => $user, 'profile' => $profile]);

В представлении используйте разные префиксы для полей:

<?php $form = ActiveForm::begin(); ?>
<?= $form->field($user, 'username') ?>
<?= $form->field($user, 'password')->passwordInput() ?>
<?= $form->field($profile, 'first_name') ?>
<?= $form->field($profile, 'last_name') ?>
<button type="submit" class="btn btn-primary">Зарегистрироваться</button>
<?php ActiveForm::end(); ?>

Обратите внимание: поля профиля автоматически получат префикс "Profile" благодаря тому, что передается объект модели.

КомпонентОписаниеПример использования
ActiveFormБазовый класс для создания формActiveForm::begin()
ActiveFieldОтдельное поле формы$form->field($model, 'attr')
DynamicModelМодель без таблицы, для динамических данныхnew DynamicModel(['field1', 'field2'])

AJAX-валидация: не даем пользователю ошибиться

Для сложных форм важно проверять данные на лету. Yii2 поддерживает AJAX-валидацию из коробки. Нужно только включить её в настройках формы.

В представлении добавьте опцию enableAjaxValidation:

<?php $form = ActiveForm::begin([
    'enableAjaxValidation' => true,
]); ?>

В контроллере обработайте AJAX-запрос:

if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
    Yii::$app->response->format = Response::FORMAT_JSON;
    return ActiveForm::validate($model);
}

Это позволит проверять каждое поле отдельно при потере фокуса. Очень удобно для длинных форм.

Лайфхак: Если у вас сложная валидация с кастомными правилами, вынесите их в отдельный метод модели. Так код будет проще поддерживать.

Кастомные виджеты: когда стандартных полей мало

Иногда нужно добавить нестандартный элемент. Например, выбор даты или загрузку файлов. В Yii2 есть множество расширений, но можно создать свой виджет.

Пример виджета для выбора цвета:

namespace app\widgets;

use yii\widgets\InputWidget;

class ColorPicker extends InputWidget
{
    public function run()
    {
        return $this->render('color-picker', [
            'model' => $this->model,
            'attribute' => $this->attribute,
        ]);
    }
}

В представлении виджета (views/widgets/color-picker.php):

<input type="color" id="<?= \yii\helpers\Html::getInputId($model, $attribute) ?>"
       name="<?= \yii\helpers\Html::getInputName($model, $attribute) ?>"
       value="<?= \yii\helpers\Html::getAttributeValue($model, $attribute) ?>">

Теперь можно использовать виджет в форме:

<?= $form->field($model, 'color')->widget(ColorPicker::class) ?>

Что в итоге

Сложные формы в Yii2 — это не страшно. Главное — разбивать задачу на части: использовать отдельные модели, динамические поля через JavaScript, AJAX-валидацию для удобства пользователя и кастомные виджеты для нестандартных элементов. Помните правило: не пытайтесь сделать все в одном файле. Дробите, структурируйте, и код станет понятным.

Надеюсь, эти примеры помогут вам в работе. Если остались вопросы — пишите в комментариях, я отвечу.

Студия WNDER