Meskipun Yii DAO secara virtual dapat menangani setiap tugas terkait-database, kenyataannya kita akan menghabiskan 90% waktu kita dalam penulisan beberapa pernyataan SQL yang melakukan operasi umum CRUD (create, read, update dan delete). Ini juga akan sulit untuk memelihara kode kita saat ia dicampur dengan pernyataan SQL. Untuk memecahkan masalah ini, kita dapat menggunakan Rekaman Aktif.
Rekaman Aktif / Active Record (AR) adalah teknik populer Pemetaan Relasional Obyek / 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-obyek. 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 kami jelaskan bagaimana menyiapkan AR dan menggunakannya untuk melakukan operasi
CRUD. Kami akan memperlihatkan bagaimana untuk menggunakan AR untuk bekerja dengan relasi database
dalam seksi berikutnya. Demi kemudahan, kami gunakan tabel database berikut
sebagai contoh kita dalam seksi ini. Catatan bahwa jika Anda menggunakan database MySQL,
Anda harus mengganti AUTOINCREMENT
dengan AUTO_INCREMENT
dalam SQL berikut.
CREATE TABLE Post ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, title VARCHAR(128) NOT NULL, content TEXT NOT NULL, createTime INTEGER NOT NULL );
Catatan: AR tidak ditujukan untuk memecahkan semua tugas-tugas terkait-database. Hal terbaik dipakai untuk memodelkan tabel database dalam konstruksi PHP dan melakukan queri yang tidak melibatkan SQL yang kompleks. Yii DAO harus dipakai untuk skenario kompleks tersebut.
AR bergantung pada koneksi DB untuk melaksanakan operasi terkait-DB. Secara standar,
ia 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 performansi
// 'schemaCachingDuration'=>3600,
),
),
);
Tip: Karena Rekaman Aktif bergantung pada metadata mengenai tabel untuk menentukan informasi kolom, dibutuhkan waktu guna membaca metadata dan menganalisanya. Jika skema database Anda kurang sepertinya perlu diubah, Anda harus menghidupkan cache skema dengan mengkonfigurasi properti CDbConnection::schemaCachingDuration ke nilai lebih besar daripada 0.
Dukungan terhadap AR dibatasi oleh DBMS. Saat ini, hanya DBMS berikut yang didukung:
Catatan: Dukungan untuk Microsoft SQL Server sudah tersedia sejak versi 1.0.4; Dan dukungan untuk Oracle sudah tersedia sejak versi 1.0.5.
Jika Anda ingin menggunakan komponen aplikasi selain db
, atau jika Anda
ingin bekerja dengan multipel database menggunakan AR, Anda harus menimpa
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
memperluas CActiveRecord. Setiap kelas AR mewakili satu tabel database,
dan turunan AR mewakili sebuah 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);
}
}
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 standar, nama kelas AR sama seperti nama tabel database. Menimpa metode tableName() jika berbeda. Metode model() dideklarasikan seperti itu untuk setiap kelas AR (akan dijelaskan kemudian).
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()
. Eksepsi akan ditampilkan
jika kita mencoba mengakses kolom yang tidak ada dengan cara yang sama.
Info: Untuk pembacaan yang lebih baik, kami sarankan penamaan tabel database dan kolom dalam jenis pundak unta. Secara khusus, nama tabel dibentuk dengan membesarkan setiap kata pada namanya dan menggabungkannya tanpa spasi; nama kolom mirip dengan nama tabel kecuali huruf pertamanya dalam jenis huruf kecil. Sebagai contoh, kita menggunakan
Post
untuk menamai tabel penyimpanan tulisan; dan kita menggunakancreateTime
untuk menamai kolom kunci primer tabel. Ini menjadikan tabel terlihat lebih seperti tipe kelas dan kolom seperti variabel. Catatan, akan tetapi, pemakaian punduk unta juga membuat Anda tidak nyaman pada beberapa DBMS, seperti misalnya MySQL, karena ia bertindak secara berbeda pada sistem operasi yang berbeda.
Untuk menyisipkan baris baru ke dalam tabel database, kita membuat turunan baru kelas AR terkait, menyetel propertinya yang terkait dengan kolom tabel, dan memanggil metode save() untuk menyelesaikan penyisipan.
$post=new Post;
$post->title='sample post';
$post->content='content for the sample post';
$post->createTime=time();
$post->save();
Jika kunci primer table adalah auto-incremental, setelah menyisipkan turunan AR
ia akan berisi kunci primer yang baru dimutakhirkan. Dalam contoh di atas, properti
id
akan merujuk pada nilai kunci primer 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 turunan AR akan secara otomatis memiliki nilai tersebut setelah turunan dibuat. Satu cara untuk mengubah nilai standar 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
Mulai dari versi 1.0.2, atribut dapat menempatkan nilai tipe CDbExpression
sebelum rekaman disimpan (baik penyisipan ataupun pemutakhiran) 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. Sejak versi 1.0.5, 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 kunci primer 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
menghasilkan turunan 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 queri, 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 queri yang diberikan.
Ketika memanggil find
, kita menggunakan $condition
dan $params
untuk menetapkan kondisi
queri. 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));
Kita juga bisa menggunakan $condition
untuk menetapkan kondisi queri lebih kompleks
Daripada sebuah string kita membiarkan $condition
menjadi turunan 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 queri, 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 queri 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 multipel baris data sama seperti kondisi queri yang ditetapkan, kita dapat
membawanya 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);
// find all rows with the specified primary keys
$posts=Post::model()->findAllByPk($postIDs,$condition,$params);
// find all rows with the specified attribute values
$posts=Post::model()->findAllByAttributes($attributes,$condition,$params);
// find all rows using the specified SQL statement
$posts=Post::model()->findAllBySql($sql,$params);
Jika tidak ada yang sama dengan kondisi queri, 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 turunan AR dipopulasikan 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 penyisipan dan pemutakhiran. Jika turunan AR dibuat
menggunakan operator new
, pemanggilan save() akan menyisipkan
baris rekaman baru ke dalam tabel database; jika turunan AR adalah hasil dari beberapa
pemanggilan metode find
atau findAll
, memanggil save() akan
memutakhirkan baris yang sudah ada dalam tabel. Bahkan, kita dapat menggunakan
CActiveRecord::isNewRecord untuk memberitahu turunan AR baru atau tidak.
Dimungkinkan juga untuk memutakhirkan 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 kunci primer 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
subseksi sebelumnya.
Kita juga bisa menghapus baris data jika turunan AR sudah dipopulasikan 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 kunci primer yang ditetapkan
Post::model()->deleteByPk($pk,$condition,$params);
Ketika menyisipkan atau memutakhirkan baris, seringkali kita harus memeriksa apakah nilai kolom sesuai dengan aturan tertentu. Ini penting terutama jika nilai kolom disediakan oleh pengguna akhir. Secara umum, kita seharusnya tidak pernah 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, rujuk seksi Mendeklarasikan Aturan Validasi. Di bawah ini adalah alur kerja umum yang diperlukan oleh penyimpanan rekaman:
if($post->save())
{
// data benar dan disisipkan/dimutakhirkan dengan sukses
}
else
{
// data tidak benar. panggil getErrors() untuk mengambil pesan kesalahan
}
Ketika data untuk penyisipan atau pemutakhiran 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 kunci primer. Oleh karena itu, untuk membandingkan dua turunan AR, kita perlu membandingkan nilai kunci primernya, 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 kunci primer composite dalam turunan AR-nya. Kunci primer terdiri dari dua atau lebih kolom. Secara bersamaan, nilai kunci primer disajikan sebagai array dalam Yii. Properti primaryKey memberikan nilai kunci primer pada turunan AR.
CActiveRecord menyediakan beberapa metode penampungan yang dapat ditimpa 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 turunan AR.
beforeDelete dan afterDelete: ini dipanggil sebelum dan sesudah turunan AR dihapus.
afterConstruct: ini dipanggil untuk
setiap turunan AR yang dibuat menggunakan operator new
.
afterFind: ini dipanggil untuk setiap turunan AR yang dibuat sebagai hasil dari queri.
Setiap turunan 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: Dukungan lingkup bernama sudah tersedia sejak versi 1.0.5. Ide lingkup bernama berasal dari Ruby on Rails.
Sebuah named scope atau lingkup bernama mewakili kriteria queri bernama yang dapat digabung dengan lingkup bernama lain dan diterapkan ke queri rekaman aktif.
Lingkup bernama dideklarasikan terutama dalam metode CActiveRecord::scopes() sebagai pasangan nama-kriteria. Kode berikut mendeklarasikan tiga lingkup bernama, 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 lingkup bernama dideklarasikan sebagai sebuah array yang dipakai untuk menginisialisasi turunan CDbCriteria. Sebagai contoh, lingkup bernama recently
menetapkan bahwa properti order
adalah createTime DESC
dan properti limit
adalah 5 yang diterjemahkan ke dalam sebuah kriteria queri yang harus menghasilkan paling banyak 5 tulisan terbaru.
Lingkup bernama dipakai sebagai pembeda pada pemanggilan metode find
. Beberapa lingkup bernama 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, lingkup bernama harus berada di sebelah kiri pemanggilan metode find
. Masing-masing menyediakan kriteria queri, yang merupakan gabungan dengan kriteria lain, termasuk yang dioper ke pemanggilan metode find
. Hal ini mirip dengan menambahkan sebuah daftar filter ke sebuah queri.
Mulai dari versi 1.0.6, lingkup bernama juga dapat dipakai dengan metode update
dan delete
. Sebagai contoh, kode bberikut akan menghapus semua tulisan yang akhir-akhir ini diterbitkan:
Post::model()->published()->recently()->delete();
Catatan: Lingkup bernama hanya bisa dipakai dengan metode tingkat-kelas. Yakni, metode harus dipanggil menggunakan
ClassName::model()
.
Named scopes dapat diparameterkan. Sebagai contoh, kita mungkin ingin mengkustomisasi sejumlah tulisan dengan lingkup bernama recently
. Untuk melakukannya, daripada mendeklarasikan lingkup bernama dalam metode CActiveRecord::scopes, kita perlu mendefinisikan sebuah metode baru yang namanya sama seperti nama lingkup 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 standar lingkup bernama yang akan diterapkan untuk semua queri (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 queri atas konten situs, kita dapat mendefinisikan standar lingkup bernama guna memecahkan masalah ini. Untuk melakukannya, kita menimpa 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, ia akan secara otomatis menggunakan kriteria queri seperti didefinisikan di atas:
$contents=Content::model()->findAll();
Signup or Login in order to comment.