Membangun form yang terhubung langsung dengan model lewat ActiveForm: validasi di sisi server dari aturan model, penanganan error yang konsisten, serta widget GridView, DetailView, dan form widget yang menghemat ratusan baris kode untuk tabel dan halaman detail.

Di episode 5 kalian melihat pola $post->load($request->post()) && $post->save() — tapi dari mana data POST itu mengisi atribut? Jawabannya adalah form. Dan di Yii, form bukan sekadar HTML: ia terikat langsung dengan model dan aturan validasinya.
Konsep kuncinya: satu sumber kebenaran. Aturan validasi didefinisikan sekali di model (rules()), lalu dipakai untuk tiga hal sekaligus — validasi server saat save(), validasi di sisi klien (JavaScript) yang dihasilkan otomatis, dan error message yang konsisten. Di episode ini kita membangun form dengan ActiveForm, mempelajari validasi dan penanganan error, lalu melihat bagaimana widget GridView dan DetailView mengubah query menjadi antarmuka siap pakai.
ActiveForm adalah widget Yii yang me-render form HTML sekaligus menghubungkannya ke model. Setiap field dibuat dengan $form->field($model, 'namaAtribut'):
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
/** @var \yii\web\View $this */
/** @var app\models\Post $model */
/** @var yii\widgets\ActiveForm $form */
?>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'title')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'slug')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'body')->textarea(['rows' => 8]) ?>
<?= $form->field($model, 'status')->dropDownList([
'draft' => 'Draft',
'published' => 'Published',
]) ?>
<div class="form-group">
<?= Html::submitButton('Simpan', ['class' => 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>Yang terjadi di balik layar: $form->field($model, 'title') membaca aturan rules() model, lalu:
has-error pada field yang gagal validasi.string, max => 255 langsung membatasi input di browser.Aturan model rules() adalah sumber kebenaran tunggal:
public function rules(): array
{
return [
[['title', 'body', 'status'], 'required'],
[['title', 'slug'], 'string', 'max' => 255],
[['slug'], 'unique'],
[['status'], 'in', 'range' => ['draft', 'published']],
[['read_count'], 'integer', 'min' => 0],
];
}Validator bawaan yang paling sering dipakai:
| Validator | Contoh | Fungsi |
|---|---|---|
required | [['title'], 'required'] | Wajib diisi |
string | ['title', 'string', 'max' => 255] | Panjang teks |
email | ['email', 'email'] | Format email |
unique | ['slug', 'unique'] | Nilai tidak boleh duplikat |
in | ['status', 'in', 'range' => [...]] | Nilai harus salah satu pilihan |
integer / number | ['read_count', 'integer'] | Tipe angka |
compare | ['password2', 'compare', 'compareAttribute' => 'password'] | Perbandingan dua field |
match | ['username', 'match', 'pattern' => '/^[a-z]\w*$/'] | Regex |
file | ['avatar', 'file', 'extensions' => 'png, jpg'] | Upload file |
Tip
Aturan ini juga yang dipakai API di episode 10. Saat request API masuk dengan data tidak valid, save() akan gagal dan error validation tersedia di $model->getErrors(). Inilah alasan mengapa menaruh seluruh logika validasi di model — bukan di controller atau view — adalah praktik yang membayar berkali lipat.
Saat validasi gagal, dua hal yang kalian butuh kendalikan: pesan error per field dan rangkuman error. ActiveForm menangani yang pertama otomatis. Untuk yang kedua, gunakan errorSummary di atas form:
<?php $form = ActiveForm::begin(); ?>
<?= $form->errorSummary($model, [
'header' => 'Perbaiki error di bawah ini:',
]) ?>
<?= $form->field($model, 'title')->textInput() ?>
<?php ActiveForm::end(); ?>Di controller, error form ditangani lewat pola standar:
public function actionCreate()
{
$model = new Post();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
Yii::$app->session->setFlash('success', 'Post disimpan.');
return $this->redirect(['post/view', 'id' => $model->id]);
}
return $this->render('create', ['model' => $model]);
}Perhatikan: jika validasi gagal, save() mengembalikan false, halaman form di-render ulang dengan model yang sudah terisi input sebelumnya plus pesan error — tanpa satu baris kode tambahan untuk "mengembalikan data".
GridView adalah widget paling produktif di Yii: ia mengubah ActiveDataProvider menjadi tabel lengkap dengan sorting, pagination, dan filter:
use yii\data\ActiveDataProvider;
public function actionIndex(): string
{
$dataProvider = new ActiveDataProvider([
'query' => Post::find(),
'pagination' => ['pageSize' => 10],
'sort' => [
'defaultOrder' => ['created_at' => SORT_DESC],
],
]);
return $this->render('index', [
'dataProvider' => $dataProvider,
]);
}<?php
use yii\grid\GridView;
use yii\helpers\Html;
/** @var \yii\web\View $this */
/** @var yii\data\ActiveDataProvider $dataProvider */
?>
<?= GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
'id',
'title',
[
'attribute' => 'status',
'filter' => ['draft' => 'Draft', 'published' => 'Published'],
],
[
'attribute' => 'created_at',
'format' => ['date', 'php:d M Y'],
],
[
'class' => 'yii\grid\ActionColumn',
'template' => '{view} {update} {delete}',
],
],
]) ?>Kolom bisa berupa nama atribut ('title'), ekspresi, atau konfigurasi lengkap. Sorting dan pagination otomatis aktif begitu ActiveDataProvider dipakai — klik header kolom untuk sorting, navigasi pagination muncul sendiri. Dengan filter ('filter' => [...]), Yii bahkan membuat input filter di header secara otomatis.
Untuk halaman detail satu record, DetailView adalah pasangan GridView:
<?php
use yii\widgets\DetailView;
/** @var \yii\web\View $this */
/** @var app\models\Post $model */
?>
<?= DetailView::widget([
'model' => $model,
'attributes' => [
'id',
'title',
'slug',
'status',
[
'attribute' => 'body',
'format' => 'ntext',
],
[
'attribute' => 'created_at',
'format' => ['date', 'php:d M Y H:i'],
],
],
]) ?>format adalah kunci penting: 'ntext' menampilkan teks dengan newline, 'html' me-render HTML (berhati-hatilah dengan XSS — episode 17), ['date', ...] memformat tanggal, dan masih banyak lagi. DetailView otomatis menghasilkan label dari attribute name dan menampilkan nilai model.
Ketika kebutuhan melebihi widget bawaan, buat widget sendiri — misal badge status yang dipakai di beberapa tempat:
<?php
namespace app\widgets;
use yii\base\Widget;
class StatusBadge extends Widget
{
public string $status = '';
public function run(): string
{
$class = $this->status === 'published' ? 'success' : 'secondary';
return "<span class=\"badge bg-$class\">" . $this->status . "</span>";
}
}Dipakai di view: <?= \app\widgets\StatusBadge::widget(['status' => $model->status]) ?>. Widget inilah yang menjaga konsistensi tampilan tanpa mengulang markup — prinsip yang sama dengan component di framework modern lainnya.
Inti yang harus dibawa pulang:
errorSummary untuk rangkuman.ActiveDataProvider jadi tabel dengan sorting, pagination, filter.Di episode 8 selanjutnya, kita berkenalan dengan senjata produktivitas Yii: Gii, code generator — bagaimana Gii membaca struktur tabel dan langsung menghasilkan model, controller, CRUD, dan form yang siap dipakai, cara membuat template custom, serta mengapa Gii wajib dimatikan di production. Sampai jumpa di episode 8!