Belajar Yii - Forms, Validation & Widgets
Episode 7 of 27

Belajar Yii - Forms, Validation & Widgets

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.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

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: Form yang Terikat Model

ActiveForm adalah widget Yii yang me-render form HTML sekaligus menghubungkannya ke model. Setiap field dibuat dengan $form->field($model, 'namaAtribut'):

views/post/_form.php
<?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:

  • Menambahkan kelas has-error pada field yang gagal validasi.
  • Menampilkan pesan error di bawah field.
  • Menyertakan CSRF token secara otomatis (kita bahas di episode 17).
  • Menerapkan validasi JavaScript dari aturan yang sama — misal string, max => 255 langsung membatasi input di browser.

Validasi: Satu Aturan, Dua Sisi

Aturan model rules() adalah sumber kebenaran tunggal:

models/Post.php - aturan validasi
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:

ValidatorContohFungsi
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.

Penanganan Error yang Konsisten

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:

Ringkasan error 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:

Controller menangani validasi gagal
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: Tabel dari Query

GridView adalah widget paling produktif di Yii: ia mengubah ActiveDataProvider menjadi tabel lengkap dengan sorting, pagination, dan filter:

controllers/PostController.php - data provider
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,
    ]);
}
views/post/index.php
<?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.

DetailView: Halaman Detail

Untuk halaman detail satu record, DetailView adalah pasangan GridView:

views/post/view.php
<?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.

Widget Custom: Saat Built-in Kurang

Ketika kebutuhan melebihi widget bawaan, buat widget sendiri — misal badge status yang dipakai di beberapa tempat:

widgets/StatusBadge.php
<?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.

Penutup

Inti yang harus dibawa pulang:

  • ActiveForm menghubungkan form dengan model: validasi server + client dari satu sumber aturan.
  • Error ditangani per field otomatis, plus errorSummary untuk rangkuman.
  • GridView mengubah ActiveDataProvider jadi tabel dengan sorting, pagination, filter.
  • DetailView menampilkan satu record dengan format nilai yang fleksibel.
  • Widget custom menjaga tampilan tetap konsisten dan reusable.

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!