Belajar Yii - Active Record & Query Builder
Episode 5 of 27

Belajar Yii - Active Record & Query Builder

Mempelajari Active Record Yii: model dengan attributes dan aturan validasi, query AR dengan findBy/where/join, definisi relasi antar tabel, perbedaan Active Query dengan Query Builder, serta praktik CRUD lengkap yang aman dari SQL injection karena pemakaian parameter binding.

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

Pendahuluan

Sejauh ini aplikasi kalian masih "bisu" — halaman statis tanpa data. Di episode 5 ini kita membuka jalur utama antara aplikasi dan database: Active Record (AR). Inilah komponen yang paling sering kalian pakai sehari-hari di Yii, dan memahaminya secara benar akan menentukan kualitas kode kalian — baik dari sisi kebenaran maupun keamanan.

Inti pemikiran AR sederhana: satu baris tabel = satu object PHP. Kalian tidak menulis SQL mentah untuk CRUD sederhana; kalian membuat object, mengisi atributnya, lalu menyimpannya. Yii juga menyediakan Query Builder untuk query kompleks yang tetap aman karena memakai parameter binding. Di episode ini kita bedah keduanya, lalu membangun CRUD lengkap.

Model Active Record

Model AR di Yii adalah class yang mewarisi yii\db\ActiveRecord. Untuk tabel posts:

models/Post.php
<?php
 
namespace app\models;
 
use yii\db\ActiveRecord;
 
class Post extends ActiveRecord
{
    public static function tableName(): string
    {
        return 'posts';
    }
 
    public function rules(): array
    {
        return [
            [['title', 'body'], 'required'],
            [['title'], 'string', 'max' => 255],
            [['body'], 'string'],
            [['status'], 'in', 'range' => ['draft', 'published']],
        ];
    }
}
  • tableName() memberi tahu Yii tabel mana yang diwakili.
  • rules() berisi aturan validasi — daftar ini dipakai oleh form (episode 7), oleh save(), dan oleh API. Inilah satu-satunya tempat aturan didefinisikan, tidak tersebar.
  • Atribut model = kolom tabel. Yii otomatis mengisi properti class dari skema database, jadi kalian tidak perlu mendeklarasikan $id, $title, dst.

Atribut dan Scenarios

Terkadang satu model dipakai di konteks berbeda dengan aturan berbeda (misal saat create title wajib, saat update boleh kosong). Scenarios menjawab ini:

Scenario untuk konteks berbeda
<?php
 
namespace app\models;
 
use yii\db\ActiveRecord;
 
class Post extends ActiveRecord
{
    public const SCENARIO_DRAFT = 'draft';
 
    public function rules(): array
    {
        return [
            [['title'], 'required', 'on' => self::SCENARIO_DRAFT],
            [['title', 'body'], 'safe', 'on' => self::SCENARIO_DRAFT],
        ];
    }
}
 
// pemakaian
$post = new Post(['scenario' => Post::SCENARIO_DRAFT]);
$post->load(Yii::$app->request->post());

Query dengan Active Query

Yii membedakan dua cara menulis query: ActiveQuery (berbasis model AR, mengembalikan object model) dan Query Builder (berbasis tabel, mengembalikan array). Keduanya dibangun di atas yii\db\Query sehingga sintaksnya nyaris sama.

Berbagai bentuk query AR
use app\models\Post;
 
// mencari satu record
$post = Post::findOne(5);                       // WHERE id = 5
$post = Post::find()->where(['id' => 5])->one(); // bentuk eksplisit
 
// kondisi dasar
$posts = Post::find()
    ->where(['status' => 'published'])
    ->andWhere(['like', 'title', 'Yii'])
    ->orderBy(['created_at' => SORT_DESC])
    ->limit(10)
    ->all();
 
// menghitung
$count = Post::find()->where(['status' => 'published'])->count();

Kunci keamanan yang sering diabaikan: jangan pernah menulis query mentah dengan interpolasi string:

Contoh rentan SQL injection (JANGAN ditiru)
$posts = Post::find()->where("title = '$title'")->all(); // berbahaya!
Cara aman dengan binding
$posts = Post::find()->where(['title' => $title])->all();        // nilai di-bind otomatis
$posts = Post::find()->where('title = :t', ['t' => $title])->all(); // binding eksplisit

Dengan bentuk array, Yii meng-escape nilai secara otomatis. SQL injection di Yii biasanya terjadi karena kalian menulis string mentah tanpa binding — kita bahas lengkap di episode 17.

Relasi Antar Tabel

Daya tarik utama AR adalah mendeklarasikan relasi sebagai method. Ambil contoh tabel posts dan comments:

Relasi di Post dan Comment
<?php
 
namespace app\models;
 
use yii\db\ActiveRecord;
 
class Post extends ActiveRecord
{
    public function getComments(): \yii\db\ActiveQuery
    {
        return $this->hasMany(Comment::class, ['post_id' => 'id']);
    }
 
    public function getAuthor(): \yii\db\ActiveQuery
    {
        return $this->hasOne(User::class, ['id' => 'user_id']);
    }
}
Memakai relasi
$post = Post::findOne(5);
$comments = $post->comments;      // komentar milik post 5
$author = $post->author;          // penulis post 5

Dua aturan ajaib: relasi diakses sebagai properti ($post->comments), dan kualitas method menentukan tipe relasi — hasOne untuk satu, hasMany untuk banyak, dengan pasangan kunci ['kolom_tabel_lawan' => 'kolom_tabel_sendiri'].

Untuk menghindari masalah N+1, gabungkan relasi dalam satu query dengan eager loading:

Eager loading mencegah N+1
$posts = Post::find()->with('comments')->all();
// query relasi digabung: 1 query posts + 1 query comments

Query Builder untuk Kasus Kompleks

Untuk agregasi atau query lintas tabel, Query Builder lebih ekspresif:

Query Builder dengan join dan agregasi
use yii\db\Query;
 
$rows = (new Query())
    ->select(['user_id', 'COUNT(*) AS total'])
    ->from('comments')
    ->where(['status' => 'approved'])
    ->groupBy('user_id')
    ->having(['>', 'total', 5])
    ->orderBy(['total' => SORT_DESC])
    ->all();

Hasilnya array associative — bukan object model. Gunakan Query Builder saat kalian hanya butuh data mentah, dan Active Query saat kalian butuh object model beserta relasinya.

Tip

Aturan praktis memilih: Active Query ketika hasilnya akan dipakai dengan relasi atau diproses lebih lanjut sebagai object; Query Builder ketika hanya butuh satu baris data untuk laporan atau agregasi. Untuk debugging query, aktifkan components.db.enableQueryLog atau pakai Debug Toolbar (episode 13) agar SQL yang dihasilkan terlihat transparan.

Praktik: CRUD Lengkap dengan AR

Mari satukan semuanya dalam satu alur CRUD. Controller:

controllers/PostController.php - CRUD
<?php
 
namespace app\controllers;
 
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
 
class PostController extends Controller
{
    public function actionIndex(): string
    {
        $posts = Post::find()
            ->where(['status' => 'published'])
            ->orderBy(['created_at' => SORT_DESC])
            ->all();
 
        return $this->render('index', ['posts' => $posts]);
    }
 
    public function actionCreate()
    {
        $post = new Post();
        if ($post->load(Yii::$app->request->post()) && $post->save()) {
            Yii::$app->session->setFlash('success', 'Post disimpan.');
            return $this->redirect(['post/view', 'id' => $post->id]);
        }
        return $this->render('create', ['model' => $post]);
    }
 
    public function actionDelete(int $id)
    {
        $post = $this->findModel($id);
        $post->delete();
        Yii::$app->session->setFlash('success', 'Post dihapus.');
        return $this->redirect(['post/index']);
    }
 
    protected function findModel(int $id): Post
    {
        $post = Post::findOne($id);
        if ($post === null) {
            throw new NotFoundHttpException('Post tidak ditemukan.');
        }
        return $post;
    }
}

Perhatikan pola load() + save(): load() mengisi atribut model dari request POST sesuai safe attributes, lalu save() menjalankan validasi dan menyimpan jika lolos. Satu method untuk create maupun update — prinsip yang membuat kode CRUD tetap pendek.

Penutup

Inti yang harus dibawa pulang:

  • Satu baris tabel = satu object AR; model punya tableName(), rules(), dan method relasi.
  • Scenarios memisahkan aturan validasi per konteks.
  • Selalu pakai parameter binding (['title' => $title]) — jangan interpolasi string.
  • Relasi hasOne/hasMany diakses sebagai properti; gunakan with() untuk eager loading.
  • load() + save() adalah pola standar CRUD; findOne() + NotFoundHttpException untuk detail.

Di episode 6 selanjutnya, kita membahas migrations & database — bagaimana skema database dikelola sebagai kode yang bisa di-versioning, membuat dan mengubah tabel dengan yii migrate, serta seeding data awal. Inilah fondasi yang membuat kolaborasi tim dan deployment otomatis menjadi mulus!

Belajar Yii - Active Record & Query Builder | Belajar Yii