Meskipun Yii DAO secara virtual dapat menangani setiap tugas terkait database, kemungkinan kita akan menghabiskan 90% waktu kita dalam penulisan beberapa pernyataan SQL yang melakukan operasi umum CRUD (create, read, update dan delete) tetap ada. Pemeliharaan kode kita saat dicampur dengan pernyataan SQL juga akan menambah kesulitan. Untuk memecahkan masalah ini, kita dapat menggunakan Active Record.
Active Record (AR) adalah teknik populer Pemetaan Relasional Objek / Object-Relational Mapping (ORM).
Setiap kelas AR mewakili tabel database (atau view) yang atributnya diwakili
oleh properti kelas AR, dan turunan AR mewakili baris pada tabel
tersebut. Operasi umum CRUD diimplementasikan sebagai metode AR. Hasilnya,
kita dapat mengakses data dengan cara lebih terorientasi-objek. Sebagai contoh,
kita dapat menggunakan kode berikut untuk menyisipkan baris baru ke dalam tabel Post
:
$post=new Post;
$post->title='sample post';
$post->content='post body content';
$post->save();
Selanjutnya kita mendalami bagaimana menyiapkan AR dan menggunakannya untuk melakukan operasi
CRUD. Kita akan melihat bagaimana untuk menggunakan AR untuk bekerja dengan relasi database
dalam seksi berikutnya. Demi kemudahan, kami menggunakan tabel database berikut
sebagai contoh kita dalam bagian ini. Harap dicatat bahwa jika Anda menggunakan database MySQL,
Anda harus mengganti AUTOINCREMENT
dengan AUTO_INCREMENT
dalam SQL berikut.
CREATE TABLE tbl_post ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, title VARCHAR(128) NOT NULL, content TEXT NOT NULL, create_time INTEGER NOT NULL );
Catatan: AR tidak ditujukan untuk memecahkan semua tugas-tugas terkait-database. AR paling cocok dipakai untuk memodelkan tabel database dalam konstruksi PHP dan melakukan query yang tidak melibatkan SQL yang kompleks. Yii DAO baru dipakai untuk skenario kompleks tersebut.
AR bergantung pada koneksi DB untuk melaksanakan operasi terkait-DB. Secara default,
AR menganggap bahwa komponen aplikasi db
adalah turunan
CDbConnection yang dibutuhkan untuk bertindak sebagai koneksi DB. Konfigurasi
aplikasi berikut memperlihatkan sebuah contoh:
return array(
'components'=>array(
'db'=>array(
'class'=>'system.db.CDbConnection',
'connectionString'=>'sqlite:path/to/dbfile',
// hidupkan cache schema untuk meningkatkan kinerja
// 'schemaCachingDuration'=>3600,
),
),
);
Tip: Karena Active Record bergantung pada metadata mengenai tabel untuk menentukan informasi kolom, dibutuhkan waktu untuk membaca metadata dan menganalisanya. Jika skema database Anda sepertinya kurang perlu diubah, Anda bisa menghidupkan cache skema (schema caching) dengan mengkonfigurasi properti CDbConnection::schemaCachingDuration ke nilai lebih besar daripada 0.
Dukungan terhadap AR dibatasi oleh DBMS. Saat ini, hanya DBMS berikut yang didukung:
Jika Anda ingin menggunakan komponen aplikasi selain db
, atau jika Anda
ingin bekerja dengan multipel database menggunakan AR, Anda harus meng-override
CActiveRecord::getDbConnection(). Kelas CActiveRecord adalah basis kelas
untuk semua kelas AR.
Tip: Ada dua cara untuk bekerja dengan multipel database dalam AR. Jika skema database berbeda, Anda dapat membuat basis kelas AR yang berbeda dengan implementasi berbeda pada getDbConnection(). Sebaliknya, secara dinamis mengubah variabel static CActiveRecord::db merupakan ide yang lebih baik.
Untuk mengakses tabel database, pertama kita perlu mendefinisikan kelas AR dengan
menurun CActiveRecord. Setiap kelas AR mewakili satu tabel database,
dan instance AR mewakili sebuah record (baris) dalam tabel tersebut. Contoh berikut
memperlihatkan kode minimal yang diperlukan untuk kelas AR yang mewakili
tabel Post
.
class Post extends CActiveRecord
{
public static function model($className=__CLASS__)
{
return parent::model($className);
}
public function tableName()
{
return 'tbl_post';
}
}
Tip: Karena kelas AR sering dirujuk di banyak tempat, kita dapat mengimpor seluruh direktori yang berisi kelas AR, daripada menyertakannya satu demi satu. Sebagai contoh, jika semua file kelas AR kita di bawah
protected/models
, kita dapat mengkonfigurasi aplikasi sebagai berikut:return array( 'import'=>array( 'application.models.*', ), );
Secara default, nama kelas AR sama dengan nama tabel database. Meng-override metode tableName() jika berbeda. Metode model() dideklarasikan seperti itu untuk setiap kelas AR (akan dijelaskan kemudian).
Info: Untuk menggunakan fitur prefiks tabel, metode tableName() untuk kelas AR harus di-override demikian,
public function tableName() { return '{{post}}'; }
Alih-alih mengembalikan nama tabel secara lengkap, kita mengembalikan nama tabel tanpa prefiks dan membungkusnya dengan kurung kurawal ganda.
Nilai kolom pada baris tabel dapat diakses sebagai properti
turunan kelas AR terkait. Sebagai contoh, kode berikut menyetel kolom
title
(atribut):
$post=new Post;
$post->title='a sample post';
Meskipun kita tidak pernah mendeklarasikan properti title
secara eksplisit dalam kelas Post
,
kita masih dapat mengaksesnya dalam kode di atas. Ini disebabkan title
adalah
kolom dalam tabel Post
, dan CActiveRecord membuatnya bisa diakses seperti layaknya
properti dengan bantuan metode magic PHP __get()
. Exception (exception) akan ditampilkan
jika kita mencoba mengakses kolom yang tidak ada, dengan cara yang sama.
Info: Di dalam panduan ini, kita menggunakan huruf kecil untuk seluruh nama tabel dan kolom. Ini dikarenakan berbeda-bedanya cara DBMS dalam penanganan case-sensitive. Contohnya PostgreSQL tidak memperlakukan nama kolom case-sensitive (jadi huruf besar sama dengan huruf kecil) secara default, dan kita harus memberi tanda kutip pada kolom dalam query jika namanya memang mengandung campuran huruf besar dan kecil. Menggunakan huruf kecil saja akan menyelesaikan permasalahan ini.
AR bergantung pada pendefinisian primary key tabel yang baik. Jika sebuah tabel tidak memiliki primary key,
maka AR membutuhkan kelas AR menentukan kolom mana yang dijadikan primary key dengan meng-overide fungsi primaryKey()
sebagai berikut,
public function primaryKey()
{
return 'id';
// Kalau composite primary key, mak kembalikan nilai array seperti demikian
// return array('pk1', 'pk2');
}
Untuk melakukan insert baris baru ke dalam tabel database, kita membuat instance baru dari kelas AR terkait, menyetel propertinya yang berkaitan dengan kolom tabel, dan memanggil metode save() untuk menyelesaikan proses insert.
$post=new Post;
$post->title='sample post';
$post->content='content for the sample post';
$post->createTime=time();
$post->save();
Jika primary key table bersifat auto-increment, setelah insert instance AR
maka akan berisi primary key yang baru. Dalam contoh di atas, properti
id
akan merujuk pada nilai primary key tulisan yang baru saja disisipkan,
meskipun kita tidak pernah mengubahnya secara eksplisit.
Jika kolom didefinisikan dengan beberapa nilai standar statis (misalnya string, angka) dalam skema tabel, properti terkait dalam instance AR akan secara otomatis memiliki nilai tersebut setelah instance dibuat. Satu cara untuk mengubah nilai default ini adalah dengan secara eksplisit mendeklarasikan properti dalam kelas AR:
class Post extends CActiveRecord
{
public $title='please enter a title';
......
}
$post=new Post;
echo $post->title; // ini akan menampilkan: please enter a title
Atribut dapat menempatkan nilai tipe CDbExpression
sebelum record disimpan (baik skenario insert ataupun update) ke database.
Sebagai contoh, untuk menyimpan timestamp yang dihasilkan oleh fungsi MySQL NOW()
,
kita dapat menggunakan kode berikut:
$post=new Post;
$post->createTime=new CDbExpression('NOW()');
// $post->createTime='NOW()'; tidak akan bekerja karena
// 'NOW()' akan dianggap sebagai string
$post->save();
Tip: Karena AR mengijinkan kita untuk melakukan operasi database tanpa menulis sejumlah pernyataan SQL, seringkali kita ingin mengetahui pernyataan SQL apa yang dijalankan oleh AR. Ini bisa dilakukan dengan menghidupkan fitur pencatatan pada Yii. Sebagai contoh, kita dapat menghidupkan CWebLogRoute dalam konfigurasi aplikasi, dan kita akan melihat pernyataan SQL yang dijalankan untuk ditampilkan di akhir setiap halaman Web. Kita dapat menyetel CDbConnection::enableParamLogging menjadi true dalam konfigurasi aplikasi agar nilai parameter yang diikat ke pernyataan SQL juga dicatat.
Untuk membaca data dalam tabel database, kita memanggil salah satu metode find
seperti
berikut.
// cari baris pertama sesuai dengan kondisi yang ditetapkan
$post=Post::model()->find($condition,$params);
// cari baris dengan primary key yang ditetapkan
$post=Post::model()->findByPk($postID,$condition,$params);
// cari baris dengan nilai atribut yang ditetapkan
$post=Post::model()->findByAttributes($attributes,$condition,$params);
// cari baris pertama menggunakan pernyataan SQL yang ditetapkan
$post=Post::model()->findBySql($sql,$params);
Dalam contoh di atas, kita memanggil metode find
dengan Post::model()
. Ingat
bahwa metode statis model()
diperlukan oleh setiap kelas AR. Metode ini
menghasilkan instance AR yang dipakai untuk mengakses metode tingkat kelas
(mirip dengan metode kelas static) dalam konteks obyek.
Jika metode find
menemukan baris yang sesuai dengan kondisi query, ia akan
mengembalikan turunan Post
yang propertinya berisi nilai kolom terkait
dari baris table. Kemudian kita dapat membaca nilai yang diambil sepert yang
kita lakukan pada properti obyek, sebagai contoh, echo $post->title;
.
Metode find
akan menghasilkan null jika tidak ada yang ditemukan dalam database
dengan kondisi query yang diberikan.
Ketika memanggil find
, kita menggunakan $condition
dan $params
untuk menetapkan kondisi
query. Di sini, $condition
dapat berupa string yang mewakili klausul WHERE
dalam
pernyataan SQL, dan $params
adalah array parameter yang nilainya harus diikat
ke tempat di dalam $condition
. Sebagai contoh,
// cari baris dengan postID=10
$post=Post::model()->find('postID=:postID', array(':postID'=>10));
Catatan: Dalam contoh di atas, kita mungkin perlu meng-escape referensi pada kolom
postID
untuk DBMS tertentu. Sebagai contoh, jika kita menggunakan PostgreSQL, kita harus menulis kondisi sebagai"postID"=:postID
, karena PostgreSQL standarnya akan memperlakukan nama kolom tidak case-sensitive.
Kita juga bisa menggunakan $condition
untuk menetapkan kondisi query lebih kompleks
Alih-alih mengisi sebuah string, kita dapat mengatur $condition
menjadi instance CDbCriteria yang
mengijinkan kita untuk menetapkan kondisi selain klausul WHERE
. Sebagai contoh,
$criteria=new CDbCriteria;
$criteria->select='title'; // hanya memilih kolom 'title'
$criteria->condition='postID=:postID';
$criteria->params=array(':postID'=>10);
$post=Post::model()->find($criteria); // $params tidak diperlukan
Catatan, saat menggunakan CDbCriteria sebagai kondisi query, parameter $params
tidak lagi diperlukan karena ia bisa ditetapkan dalam CDbCriteria, seperti dicontohkan
di atas.
Cara alternatif terhadap CDbCriteria adalah dengan mengoper array ke metode find
.
Kunci dan nilai array masing-masing berhubungan dengan properti kriteria nama dan nilai.
Contoh di atas dapat ditulis ulang seperti berikut
$post=Post::model()->find(array(
'select'=>'title',
'condition'=>'postID=:postID',
'params'=>array(':postID'=>10),
));
Info: Saat kondisi query menemukan beberapa kolom dengan nilai yang ditetapkan, kita dapat menggunakan findByAttributes(). Kita biarkan parameter
$attributes
menjadi array nilai yang diindeks oleh nama kolom. Dalam beberapa framework, tugas ini bisa dilaksanakan dengan memanggil metode sepertifindByNameAndTitle
. Meskipun pendekatan ini terlihat atraktif, sering menyebabkan kebingungan, konflik dan masalah seperti sensitifitas-jenis huruf pada nama-nama kolom.
Ketika lebih dari satu baris data memenuhi kondisi query yang ditetapkan, kita dapat
mengambilnya sekaligus menggunakan metode findAll
, masing-masing memiliki
pasangan metode find
, seperti yang sudah kami jelaskan.
// cari seluruh baris yang sesuai dengan kondisi yang ditetapkan
$posts=Post::model()->findAll($condition,$params);
// cari seluruh baris dengan primary key yang ditetapkan
$posts=Post::model()->findAllByPk($postIDs,$condition,$params);
// cari seluruh baris dengan nilai atribut yang ditetapkan
$posts=Post::model()->findAllByAttributes($attributes,$condition,$params);
// cari seluruh baris dengan pernyataan SQL yang ditetapkan
$posts=Post::model()->findAllBySql($sql,$params);
Jika tidak ada yang sama dengan kondisi query, findAll
akan mengembalikan array
kosong. Ini berbeda dengan find
yang akan mengembalikan null jika tidak menemukan
apapun.
Selain metode find
dan findAll
seperti dijelaskan di atas, metode berikut
juga disediakan demi kenyamanan:
// ambil sejumlah baris yang sesuai dengan kondisi yang ditetapkan
$n=Post::model()->count($condition,$params);
// ambil sejumlah baris menggunakan pernyataan SQL yang ditetapkan
$n=Post::model()->countBySql($sql,$params);
// periksa apakah ada satu baris yang sesuai denga kondisi yang ditetapkan
$exists=Post::model()->exists($condition,$params);
Setelah instance AR diisi dengan nilai kolom, kita dapat mengubah dan menyimpannya kembali ke tabel database.
$post=Post::model()->findByPk(10);
$post->title='new post title';
$post->save(); // simpan perubahan ke database
Seperti yang kita lihat, kita menggunakan metode save() yang sama
untuk melakukan operasi insert maupun update. Jika instance AR dibuat
menggunakan operator new
, pemanggilan save() akan menyisipkan
baris record baru ke dalam tabel database; jika turunan AR adalah hasil dari beberapa
pemanggilan metode find
atau findAll
, memanggil save() akan
mengupdate baris yang sudah ada dalam tabel. Bahkan, kita dapat menggunakan
CActiveRecord::isNewRecord untuk mengetahui apakah turunan AR baru atau tidak.
Dimungkinkan juga untuk mengupdate satu atau beberapa baris dalam sebuah tabel database tanpa memanggilnya lebih dulu. AR menyediakan metode tingkat-kelas yang nyaman untuk tujuan ini:
// mutakhirkan baris yang sama seperti kondisi yang ditetapkan
Post::model()->updateAll($attributes,$condition,$params);
// mutakhirkan baris yang sama seperti kondisi dan primary key yang ditetapkan
Post::model()->updateByPk($pk,$attributes,$condition,$params);
// mutakhirkan kolom counter dalam baris yang sesuai dengan kondisi yang ditetapkan
Post::model()->updateCounters($counters,$condition,$params);
Dalam contoh di atas, $attributes
adalah array nilai kolom yang diindeks oleh
nama kolom; $counters
adalah array nilai inkremental yang diindeks oleh nama
kolom; sedangkan $condition
dan $params
seperti yang sudah dijelaskan dalam
sub-bab sebelumnya.
Kita juga bisa menghapus baris data jika turunan AR sudah diisi dengan baris ini.
$post=Post::model()->findByPk(10); // menganggap ada tulisan yang memiliki ID = 10
$post->delete(); // hapus baris dari tabel database
Catatan, setelah penghapusan, turunan AR tetap tidak berubah, tapi baris terkait dalam tabel database sudah tidak ada.
Metode tingkat kelas berikut disediakan untuk menghapus baris tanpa haris mengambilnya lebih dulu:
// hapus baris yang sesuai dengan kondisi yang ditetapkan
Post::model()->deleteAll($condition,$params);
// hapus baris yang sesuai dengan kondisi dan primary key yang ditetapkan
Post::model()->deleteByPk($pk,$condition,$params);
Ketika melakukan insert atau update baris, seringkali kita harus memeriksa apakah nilai kolom sesuai dengan aturan tertentu. Hal ini penting terutama jika nilai kolom diberikan oleh pengguna akhir. Pada dasarnya, kita seharusnya tidak boleh mempercayai apapun yang berasal dari sisi klien.
AR melakukan validasi data secara otomatis ketika save() sedang dipanggil. Validasi didasarkan pada aturan yang ditetapkan dalam metode rules() pada kelas AR. Untuk lebih jelasnya mengenai bagaimana untuk menetapkan aturan validasi, silahkan merujuk ke bagian Mendeklarasikan Aturan Validasi. Di bawah ini adalah alur kerja umum yang diperlukan oleh penyimpanan record:
if($post->save())
{
// data benar dan insert/update dengan sukses
}
else
{
// data tidak benar. panggil getErrors() untuk mengambil pesan kesalahan
}
Ketika data untuk insert atau update dikirimkan oleh pengguna akhir dalam bentuk HTML, kita perlu menempatkannya ke properti AR terkait. Kita apat melakukannya seperti berikut:
$post->title=$_POST['title'];
$post->content=$_POST['content'];
$post->save();
Jika terdapat banyak kolom, kita akan melihat daftar yang panjang dari penempatan tersebut. Ini dapat dipersingkat dengan pemakaian properti attributes seperti ditampilkan di bawah. Rincian dapat ditemukan dalam seksi Mengamankan Penempatan Atribut dan seksi Membuat Aksi.
// anggap $_POST['Post'] adalah array nilai kolom yang diindeks oleh nama kolom
$post->attributes=$_POST['Post'];
$post->save();
Seperti baris tabel, turunan AR secara unik diidentifikasi dengan nilai primary key. Oleh karena itu, untuk membandingkan dua instance AR, kita perlu membandingkan nilai primary key-nya, menganggap keduanya milik kelas AR yang sama. Cara paling sederhana adalah dengan memanggil CActiveRecord::equals().
Info: Tidak seperti implementasi AR dalam framework lain, Yii mendukung primary key composite dalam turunan AR-nya. Kunci primer terdiri dari dua atau lebih kolom. Secara bersamaan, nilai primary key disajikan sebagai array dalam Yii. Properti primaryKey memberikan nilai primary key pada turunan AR.
CActiveRecord menyediakan beberapa metode penampungan yang dapat di-overide dalam anak kelas untuk mengkustomisasi alur kerjanya.
beforeValidate dan afterValidate: ini dipanggil sebelum dan sesudah validasi dilakukan.
beforeSave dan afterSave: ini dipanggil sebelum dan sesudah penyimpanan instance AR.
beforeDelete dan afterDelete: ini dipanggil sebelum dan sesudah instance AR dihapus.
afterConstruct: ini dipanggil untuk
setiap instance AR yang dibuat menggunakan operator new
.
beforeFind: ini dipanggil sebelum pencari AR
dipakai untuk melakukan query (misal find()
, findAll()
).
afterFind: ini dipanggil untuk setiap instance AR yang dibuat sebagai hasil dari query.
Setiap instance AR berisi properti bernama dbConnection yang merupakan turunan dari CDbConnection. Kita dapat menggunakan fitur transaksi yang disediakan oleh Yii DAO jika diinginkan saat bekerja dengan AR:
$model=Post::model();
$transaction=$model->dbConnection->beginTransaction();
try
{
// cari dan simpan adalah dua langkah yang bisa diintervensi oleh permintaan lain
// oleh karenanya kita menggunakan transaksi untuk memastikan konsistensi dan integritas
$post=$model->findByPk(10);
$post->title='new post title';
$post->save();
$transaction->commit();
}
catch(Exception $e)
{
$transaction->rollBack();
}
Catatan: Ide named scope berasal dari Ruby on Rails.
Sebuah named scope mewakili kriteria query bernama yang dapat digabung dengan named scope lain dan diterapkan ke query active record.
Named scope dideklarasikan terutama dalam metode CActiveRecord::scopes() sebagai pasangan nama-kriteria. Kode berikut mendeklarasikan tiga named scope, published
dan recently
, dalam kelas model Post
class Post extends CActiveRecord
{
......
public function scopes()
{
return array(
'published'=>array(
'condition'=>'status=1',
),
'recently'=>array(
'order'=>'createTime DESC',
'limit'=>5,
),
);
}
}
Setiap named scope dideklarasikan sebagai sebuah array yang dipakai untuk menginisialisasi instance CDbCriteria. Sebagai contoh, named scope recently
menetapkan bahwa properti order
adalah createTime DESC
dan properti limit
adalah 5 yang diterjemahkan ke dalam sebuah kriteria query yang harus menghasilkan paling banyak 5 tulisan terbaru.
Named scope dipakai sebagai pembeda pada pemanggilan metode find
. Beberapa named scope dapat dikaitkan bersamaan dan menghasilkan set yang lebih terbatas. Sebagai contoh, untuk mencari tulisan terbaru yang diterbitkan, kita menggunakan kode berikut:
$posts=Post::model()->published()->recently()->findAll();
Secara umum, named scope harus berada di sebelah kiri pemanggilan metode find
. Masing-masing menyediakan kriteria query, yang merupakan gabungan dengan kriteria lain, termasuk yang dioper ke pemanggilan metode find
. Hal ini mirip dengan menambahkan sebuah daftar filter ke sebuah query.
Catatan: Named scope hanya bisa dipakai dengan metode tingkat-kelas. Yakni, metode harus dipanggil menggunakan
ClassName::model()
.
Named scope dapat diparameterkan. Sebagai contoh, kita mungkin ingin mengkustomisasi sejumlah tulisan dengan named scope recently
. Untuk melakukannya, daripada mendeklarasikan named scope dalam metode CActiveRecord::scopes, kita dapat mendefinisikan sebuah metode baru yang namanya sama seperti named scope tadi:
public function recently($limit=5)
{
$this->getDbCriteria()->mergeWith(array(
'order'=>'createTime DESC',
'limit'=>$limit,
));
return $this;
}
Selanjutnya, kita bisa menggunakan pernyataan berikut untuk mengambil 3 tulisan terbaru yang diterbitkan:
$posts=Post::model()->published()->recently(3)->findAll();
Jika kita tidak melengkapi parameter 3 di atas, secara standar kita akan mengambil 5 tulisan terbaru yang diterbitkan.
Kelas model dapat memiliki default scope yang akan diterapkan untuk semua query (termasuk yang relasional) berkenaan dengan model. Sebagai contoh, website yang mendukung multi bahasa mungkin hanya ingin menampilkan konten yang ditetapkan oleh pengguna saat ini. Karena di sana ada banyak query atas konten situs, kita dapat mendefinisikan default scope guna memecahkan masalah ini. Untuk melakukannya, kita meng-override metode CActiveRecord::defaultScope seperti berikut,
class Content extends CActiveRecord
{
public function defaultScope()
{
return array(
'condition'=>"language='".Yii::app()->language."'",
);
}
}
Sekarang, jika metode di bawah ini dipanggil, Yii akan secara otomatis menggunakan kriteria query seperti didefinisikan di atas:
$contents=Content::model()->findAll();
Note: Default scope dan named scope hanya berlaku pada query
SELECT
. Mereka akan mengabaikan queryINSERT
,UPDATE
, danDELETE
. Juga, ketika mendeklarasikan scope (baik default maupun named), kelas AR tidak dapat digunakan untuk membuat query DB di dalam method yang mendeklarasikan scope.
Found a typo or you think this page needs improvement?
Edit it on github !
Signup or Login in order to comment.