0 follower

Rekaman Aktif

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.

1. Membuat Koneksi DB

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.

2. Mendefinisikan Kelas AR

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 menggunakan createTime 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.

3. Membuat Rekaman

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.

4. Membaca Rekaman

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 seperti findByNameAndTitle. 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);

5. Memutakhirkan Rekaman

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.

6. Menghapus Rekaman

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);

7. Validasi Data

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();

8. Membandingkan Rekaman

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.

9. Kustomisasi

CActiveRecord menyediakan beberapa metode penampungan yang dapat ditimpa dalam anak kelas untuk mengkustomisasi alur kerjanya.

10. Menggunakan Transaksi dengan AR

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();
}

11. Lingkup Bernama

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().

Parameterisasi Lingkup Bernama

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.

Standar Lingkup Bernama

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();