CodeIgniter 3 terkenal ringan dan sederhana, tetapi salah satu kekurangan yang sering terasa saat aplikasi mulai besar adalah tidak adanya ORM bawaan. Selama kebutuhan masih sebatas CRUD sederhana, Query Builder bawaan CodeIgniter biasanya cukup. Namun ketika mulai berhadapan dengan relasi many-to-many, pemuatan data bertingkat, dan masalah performa seperti N+1 query, penggunaan ORM bisa membuat kode lebih rapi dan lebih mudah dirawat.
Di artikel ini kita akan bahas pendekatan praktis menggunakan ORM pada CodeIgniter 3, dengan fokus pada dua masalah nyata: many-to-many dan N+1. Contoh yang dipakai sengaja dibuat langsung ke kasus sehari-hari, misalnya relasi posts dan tags.
Mengapa ORM di CodeIgniter 3 Masih Relevan?
Secara default, CodeIgniter 3 menyediakan Query Builder, bukan ORM penuh. Artinya, Anda tetap menulis query secara eksplisit untuk relasi antar tabel. Ini tidak selalu buruk, justru sering lebih transparan. Tetapi ada beberapa kondisi di mana ORM membantu:
- Relasi data kompleks, misalnya one-to-many dan many-to-many.
- Pengurangan boilerplate saat mengambil data terkait.
- Model lebih ekspresif, misalnya
$post->tagslebih jelas daripada menulis join berulang-ulang. - Kontrol eager loading untuk menghindari N+1 query.
Untuk CodeIgniter 3, pendekatan yang umum adalah memakai ORM pihak ketiga. Dua pendekatan yang sering ditemui:
- DataMapper ORM untuk CodeIgniter lama.
- Eloquent ORM dari ekosistem Laravel, diintegrasikan secara manual ke CodeIgniter 3.
Dalam praktik modern, banyak tim lebih memilih Eloquent karena dokumentasi lebih luas, fitur relasi lengkap, dan konsepnya lebih familiar.
Setup Singkat Eloquent di CodeIgniter 3
Jika ingin memakai Eloquent di CodeIgniter 3, instal komponen yang dibutuhkan via Composer:
composer require illuminate/database:^8.0Lalu buat file library, misalnya application/libraries/Eloquent.php:
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
class Eloquent {
public function __construct() {
$capsule = new Capsule;
$capsule->addConnection([
'driver' => 'mysql',
'host' => '127.0.0.1',
'database' => 'db_ci3',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
]);
$capsule->setAsGlobal();
$capsule->bootEloquent();
}
}Kemudian autoload library ini atau load di controller dasar:
$this->load->library('Eloquent');Setelah itu, model Eloquent bisa dipakai di dalam proyek CodeIgniter 3.
Catatan: jika proyek Anda belum memakai Composer, integrasi ORM akan terasa lebih merepotkan. Untuk proyek lama, pertimbangkan apakah benar-benar perlu ORM, atau cukup optimasi Query Builder.
Kasus Many-to-Many: Post dan Tag
Struktur Tabel
Contoh paling umum untuk many-to-many adalah satu post bisa punya banyak tag, dan satu tag bisa dipakai di banyak post.
CREATE TABLE posts (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(150) NOT NULL,
content TEXT,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL
);
CREATE TABLE tags (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL
);
CREATE TABLE post_tag (
post_id INT UNSIGNED NOT NULL,
tag_id INT UNSIGNED NOT NULL,
PRIMARY KEY (post_id, tag_id),
CONSTRAINT fk_post_tag_post FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
CONSTRAINT fk_post_tag_tag FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);Tabel penghubung post_tag adalah inti dari relasi many-to-many. Primary key gabungan penting untuk mencegah duplikasi pasangan post_id dan tag_id.
Model Post dan Tag
<?php
use Illuminate\Database\Eloquent\Model;
class Post extends Model {
protected $table = 'posts';
protected $fillable = ['title', 'content'];
public function tags() {
return $this->belongsToMany(Tag::class, 'post_tag', 'post_id', 'tag_id');
}
}
class Tag extends Model {
protected $table = 'tags';
protected $fillable = ['name'];
public function posts() {
return $this->belongsToMany(Post::class, 'post_tag', 'tag_id', 'post_id');
}
}Relasi ini membuat kode menjadi jauh lebih ekspresif. Anda tidak perlu menulis join manual setiap kali ingin mengambil tag dari sebuah post.
Menyimpan Data Many-to-Many
Misalnya Anda membuat post baru lalu menghubungkannya dengan beberapa tag:
$post = Post::create([
'title' => 'Belajar ORM di CodeIgniter 3',
'content' => 'Isi artikel...'
]);
$post->tags()->attach([1, 2, 5]);Method attach() akan menambahkan relasi ke tabel pivot post_tag.
Kalau ingin update relasi sekaligus sinkronisasi, gunakan sync():
$post->tags()->sync([2, 3, 6]);Artinya, relasi lama yang tidak ada di daftar baru akan dihapus, dan relasi baru akan ditambahkan. Ini biasanya lebih aman untuk form edit.
Mengambil Data Beserta Tag
$post = Post::find(10);
foreach ($post->tags as $tag) {
echo $tag->name . '<br>';
}Secara fungsional ini nyaman, tetapi di sinilah kita perlu berhati-hati terhadap masalah N+1.
Memahami Masalah N+1 Query
N+1 query terjadi ketika aplikasi menjalankan 1 query untuk mengambil data utama, lalu menjalankan N query tambahan untuk setiap baris data terkait.
Contoh yang terlihat aman tetapi bermasalah:
$posts = Post::all();
foreach ($posts as $post) {
echo $post->title;
foreach ($post->tags as $tag) {
echo $tag->name;
}
}Yang terjadi:
- 1 query untuk mengambil semua post.
- Untuk setiap post, 1 query lagi untuk mengambil tags.
Kalau ada 100 post, total bisa menjadi 101 query. Pada data kecil mungkin tidak terasa, tetapi pada halaman listing besar performanya akan turun drastis.
Cara Mengatasinya: Eager Loading
Gunakan eager loading dengan with() agar relasi dimuat di awal:
$posts = Post::with('tags')->get();
foreach ($posts as $post) {
echo $post->title;
foreach ($post->tags as $tag) {
echo $tag->name;
}
}Dengan cara ini, ORM biasanya akan menjalankan query yang jauh lebih efisien:
- 1 query untuk posts
- 1 query untuk semua tags yang terkait
- 1 query untuk data pivot jika diperlukan oleh ORM
Jumlah query menjadi stabil dan tidak bertambah linear mengikuti jumlah post.
Filter Kolom agar Tidak Boros
Salah satu kesalahan umum saat memakai eager loading adalah mengambil semua kolom padahal yang dibutuhkan hanya sedikit. Anda bisa membatasi kolom:
$posts = Post::with(['tags:id,name'])
->select('id', 'title')
->get();Ini membantu mengurangi payload, terutama jika tabel utama punya kolom besar seperti content atau metadata lain.
Contoh Langsung di Controller CodeIgniter 3
Berikut contoh controller sederhana untuk menampilkan daftar post dan tag tanpa N+1:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Posts extends CI_Controller {
public function __construct() {
parent::__construct();
$this->load->library('Eloquent');
}
public function index() {
$posts = Post::with(['tags:id,name'])
->select('id', 'title', 'created_at')
->orderBy('id', 'desc')
->get();
foreach ($posts as $post) {
echo '<h3>' . html_escape($post->title) . '</h3>';
echo '<ul>';
foreach ($post->tags as $tag) {
echo '<li>' . html_escape($tag->name) . '</li>';
}
echo '</ul>';
}
}
}Jika Anda tetap memakai view terpisah, kirim saja objek $posts ke view seperti biasa. Yang penting, relasi sudah dimuat sebelum rendering.
Kapan ORM Lebih Baik, Kapan Query Builder Lebih Cocok?
Pilih ORM jika:
- Aplikasi punya banyak relasi antar entitas.
- Anda ingin model yang lebih bersih dan mudah dibaca.
- Butuh eager loading untuk mengatasi N+1 dengan cara yang konsisten.
- Tim sudah familiar dengan pola Active Record atau Eloquent.
Pilih Query Builder jika:
- Query sangat spesifik dan kompleks.
- Butuh kontrol SQL yang lebih eksplisit.
- Proyek kecil dan relasinya sederhana.
- Anda ingin meminimalkan dependency tambahan di CodeIgniter 3.
Dalam banyak proyek nyata, pendekatan terbaik justru campuran: gunakan ORM untuk relasi umum, tetapi tetap pakai Query Builder atau raw SQL untuk laporan, agregasi berat, atau query yang sangat khusus.
Kesalahan Umum dan Tips Debugging
1. Relasi many-to-many salah urutan key
Kesalahan paling sering adalah parameter foreign key tertukar di belongsToMany(). Jika data tidak muncul, cek kembali:
return $this->belongsToMany(Tag::class, 'post_tag', 'post_id', 'tag_id');Urutannya adalah: model tujuan, nama tabel pivot, foreign key milik model saat ini, lalu foreign key milik model relasi.
2. Lupa eager loading
Kode tetap jalan, tetapi query meledak diam-diam. Biasakan review bagian loop yang mengakses relasi seperti $post->tags. Jika berada dalam iterasi data utama, hampir selalu perlu with().
3. Pivot table tidak punya indeks yang benar
Untuk many-to-many, tabel pivot harus punya indeks atau primary key gabungan. Tanpa itu, pencarian dan join akan melambat saat data membesar.
4. Terlalu banyak data dimuat sekaligus
Eager loading memang menyelesaikan N+1, tetapi bukan berarti semua relasi harus dimuat. Ambil hanya yang dibutuhkan. Untuk data besar, gunakan paginasi:
$posts = Post::with('tags')->paginate(20);Jika integrasi paginasi bawaan CI3 dan Eloquent tidak cocok, minimal gunakan limit dan offset secara manual.
5. Sulit melihat query yang sebenarnya dijalankan
Saat debugging performa, aktifkan query log dari database layer yang Anda pakai, atau tambahkan profiler. Tujuannya untuk memastikan apakah kode menghasilkan 2-3 query yang efisien, atau justru puluhan query kecil.
Penutup
Meskipun CodeIgniter 3 tidak punya ORM bawaan, Anda tetap bisa memakai ORM seperti Eloquent untuk menangani relasi yang lebih kompleks dengan kode yang lebih bersih. Untuk kasus many-to-many, ORM sangat membantu karena pengelolaan tabel pivot menjadi jauh lebih sederhana. Untuk masalah N+1 query, kuncinya adalah memahami kapan relasi dipanggil dan menggunakan eager loading secara sadar.
Kalau aplikasi Anda mulai penuh relasi seperti post-tag, user-role, product-category, atau article-label, ORM di CodeIgniter 3 bisa menjadi peningkatan yang signifikan. Namun tetap gunakan secara pragmatis: tidak semua query harus dipaksa lewat ORM. Fokus utamanya tetap sama, yaitu kode yang jelas, query yang efisien, dan struktur aplikasi yang mudah dirawat.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!