Forms with ActiveForm
ActiveForm is a widget that builds an HTML form from a model: it creates fields with labels, shows validation errors and adds protection against request forgery. A widget in Yii is a ready-made interface block called in a view.
The controller action
One action both shows the form and accepts it. Add to controllers/PostController.php:
// controllers/PostController.php
public function actionCreate()
{
$post = new Post();
if ($post->load(Yii::$app->request->post()) && $post->save()) {
return $this->redirect(['post/view', 'id' => $post->id]);
}
return $this->render('create', ['post' => $post]);
}load() fills the model with form data and returns false if the form was not submitted. save() validates and writes. On success the visitor goes to the post page, otherwise the form is shown again, now with errors.
The form view
Create the file views/post/create.php:
<?php
// views/post/create.php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
$this->title = 'New post';
?>
<h1><?= Html::encode($this->title) ?></h1>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($post, 'title') ?>
<?= $form->field($post, 'body')->textarea(['rows' => 8]) ?>
<?= Html::submitButton('Save', ['class' => 'btn btn-primary']) ?>
<?php ActiveForm::end(); ?>Open /post/create. The field() call creates a label from attributeLabels(), an input and a place for the error. Submit an empty form and messages from the model rules appear under the fields. In the standard project you can use yii\bootstrap5\ActiveForm instead of yii\widgets\ActiveForm, and the form gets Bootstrap styling.
The user_id, created_at and updated_at columns are not filled yet. Until the lesson on behaviors, set them in the action before save() or give them default values in a migration.
Kinds of fields
| Call | Field |
|---|---|
$form->field($post, 'title') | A text line |
->textarea(['rows' => 8]) | Multi-line text |
->passwordInput() | A password |
->dropDownList([0 => 'Draft', 1 => 'Published']) | A drop-down list |
->checkbox() | A checkbox |
->hint('Up to 255 characters') | A hint under the field |
->label('Name') | Your own label |
Updating a record
The update form differs only in that the model is taken from the database:
public function actionUpdate(int $id)
{
$post = Post::findOne($id);
if ($post === null) {
throw new NotFoundHttpException('Post not found.');
}
if ($post->load(Yii::$app->request->post()) && $post->save()) {
return $this->redirect(['post/view', 'id' => $post->id]);
}
return $this->render('update', ['post' => $post]);
}The fields come filled with the current values. To avoid repeating markup, the fields are moved into a shared file views/post/_form.php, which is inserted into create.php and update.php with $this->render('_form', ['post' => $post]).
Protection against request forgery
ActiveForm adds a hidden _csrf field with a random value to the form. Yii checks it on every POST request and rejects forms sent from other sites.
Common mistake
A form written by hand with tags sends fields named like title. But load() expects data grouped under the model name: Post[title], Post[body]. Such a form "does not save" without a single error. Build fields with $form->field() and the names will be right.