Menguasai mekanisme ekstensibilitas Yii: mendefinisikan event dan mengikat handler secara dinamis maupun statis, behavior sebagai cara menempelkan kode ke komponen, event yang dipicu model Active Record, serta cara membuat dan mempublikasikan extension Yii sebagai package Composer.

Di episode 2 kita menyebut event system dan behaviors sebagai fondasi — dan sejak episode 10 kalian sudah memakai behavior (TimestampBehavior, BlameableBehavior) tanpa memikirkan cara kerjanya. Sekarang saatnya membongkar mekanisme itu, karena begitu kalian memahaminya, kalian tidak lagi sekadar memakai Yii — kalian mulai memperpanjang Yii.
Di episode ini kita membahas tiga hal yang berlapis: events (sistem pemicu-reaksi), behaviors (cara menempelkan kode ke komponen), dan extensions (mengemas kemampuan itu menjadi package Composer yang bisa dipakai ulang dan dibagikan). Ini adalah garis antara "developer framework" dan "developer yang menggunakan framework".
Event adalah mekanisme longgar-terhubung: satu bagian aplikasi memicu kejadian, bagian lain mendengarkan dan bereaksi — tanpa saling tahu. Yii menyediakan event di setiap component:
use yii\db\ActiveRecord;
// mendengarkan event pada satu instance
$post = new Post();
$post->on(ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
Yii::info("Post baru dibuat: " . $event->sender->id, 'app\post');
});
// memicu dari dalam model
$this->trigger(ActiveRecord::EVENT_AFTER_INSERT);$event->sender selalu merujuk object yang memicu event — jadi handler bisa membaca konteks tanpa referensi eksternal.
on() berlaku untuk satu instance. Untuk mendengarkan event dari semua instance sekaligus, gunakan Event::on — biasanya di bootstrap aplikasi:
<?php
use yii\base\Event;
use yii\db\ActiveRecord;
Event::on(Post::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
/** @var Post $post */
$post = $event->sender;
Yii::$app->cache->delete('latest_posts');
});Dengan handler global seperti ini, invalidasi cache berjalan otomatis untuk post apa pun yang dibuat di mana pun — tanpa menyentuh controller atau model. Inilah kekuatan event: cross-cutting concern dipasang sekali di satu tempat.
Model AR memicu event di setiap tahap siklus hidupnya — inilah yang membuat behaviors di episode 10 bekerja:
| Event | Kapan Terpicu |
|---|---|
EVENT_BEFORE_INSERT / EVENT_AFTER_INSERT | Sebelum / sesudah insert() |
EVENT_BEFORE_UPDATE / EVENT_AFTER_UPDATE | Sebelum / sesudah update() |
EVENT_BEFORE_DELETE / EVENT_AFTER_DELETE | Sebelum / sesudah delete() |
EVENT_BEFORE_VALIDATE / EVENT_AFTER_VALIDATE | Sebelum / sesudah validasi |
EVENT_BEFORE_SAVE / EVENT_AFTER_SAVE | Menyala untuk insert dan update |
Handler bisa membatalkan operasi dengan mengatur $event->isValid = false (seperti softDelete di episode 10).
Behavior adalah object yang me-reuse method dan event handler ke component yang dipasanginya — mirip trait, tapi dinamis: bisa dipasang/dilepas saat runtime, dan tidak mengubah class asli.
<?php
namespace app\behaviors;
use yii\base\Behavior;
class LoggableBehavior extends Behavior
{
public string $logMessage = 'Aksi dilakukan';
public function log(string $action): void
{
Yii::info("{$this->logMessage}: $action", 'app\audit');
}
}use app\behaviors\LoggableBehavior;
public function behaviors(): array
{
return [
[
'class' => LoggableBehavior::class,
'logMessage' => 'Post diakses',
],
];
}
// method behavior bisa dipanggil seperti method model
$post->log('view');Method, properties, dan event handler yang dimiliki behavior menjadi milik component tempat ia dipasang. $post->log('view') berfungsi karena behavior terpasang. Inilah mengapa TimestampBehavior bisa mengisi created_at tanpa model mendeklarasikan apa pun: behavior tersebut menempelkan event handler ke EVENT_BEFORE_INSERT.
| Pendekatan | Gunakan Ketika |
|---|---|
| Event | Satu bagian kode perlu bereaksi terhadap kejadian di bagian lain, tanpa bergantung langsung |
| Behavior | Kumpulan kemampuan yang reusable ingin dipasang ke banyak model/component secara dinamis |
| Trait | Kemampuan statis yang pasti dipakai semua class (tanpa konfigurasi per instance) |
Ketika suatu kemampuan sudah dipakai di banyak project, saatnya mengemasnya sebagai extension — package Composer yang bisa diinstall di project mana pun. Struktur extension Yii mengikuti standar Yii extension:
yii2-audit-log/
├── src/
│ ├── AuditBehavior.php
│ └── AuditTarget.php
├── tests/
├── composer.json
└── README.mdcomposer.json extension adalah kuncinya — Yii membaca konfigurasi dari properti khusus:
{
"name": "vendor/yii2-audit-log",
"description": "Audit log behavior untuk Yii 2",
"type": "yii2-extension",
"require": {
"yiisoft/yii2": "~2.0.0",
"php": ">=7.4"
},
"autoload": {
"psr-4": {
"vendor\\auditlog\\": "src/"
}
},
"extra": {
"bootstrap": "vendor\\auditlog\\Bootstrap"
}
}type: yii2-extension memberi tahu Composer bahwa ini extension Yii.autoload.psr-4 memetakan namespace vendor\auditlog\ ke folder src/.extra.bootstrap adalah hook: class Bootstrap akan dijalankan otomatis saat aplikasi start (tempat memasang Event::on global).<?php
namespace vendor\auditlog;
use yii\base\Application;
use yii\base\BootstrapInterface;
use yii\base\Event;
use yii\db\ActiveRecord;
class Bootstrap implements BootstrapInterface
{
public function bootstrap($app): void
{
Event::on(ActiveRecord::class, ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
AuditLog::record($event->sender);
});
}
}Dengan BootstrapInterface, extension kalian "hidup" begitu terdaftar di config/web.php — tanpa user perlu memanggil apa pun.
Untuk memakai extension antar project (tanpa publikasi publik), gunakan repository path di composer.json project:
"repositories": [
{
"type": "path",
"url": "../yii2-audit-log"
}
]composer require vendor/yii2-audit-log:@devUntuk publikasi publik, push ke GitHub lalu daftarkan di Packagist — service yang menyediakan package Composer. Ikuti SemVer (2.0.0, 2.1.0 untuk fitur baru, 2.1.1 untuk bugfix) dan tulis README yang baik: instalasi, konfigurasi, contoh pemakaian.
Important
Sebelum mengemas sesuatu menjadi extension, pertimbangkan: apakah ini benar-benar dipakai lintas project? Extension adalah kontrak jangka panjang — begitu dipublikasikan, perubahan breaking akan menyakiti pengguna. Mulailah dengan behavior/component dalam project, dan ekstrak menjadi extension setelah terbukti stabil di beberapa project nyata.
Satukan konsep dalam studi kasus nyata: extension audit log yang mencatat setiap perubahan model. User cukup memasang behavior:
<?php
namespace vendor\auditlog;
use yii\base\Behavior;
use yii\db\ActiveRecord;
class AuditBehavior extends Behavior
{
public function events(): array
{
return [
ActiveRecord::EVENT_AFTER_INSERT => 'recordInsert',
ActiveRecord::EVENT_AFTER_UPDATE => 'recordUpdate',
ActiveRecord::EVENT_AFTER_DELETE => 'recordDelete',
];
}
public function recordInsert($event): void
{
AuditLog::write($this->owner, 'insert');
}
public function recordUpdate($event): void
{
AuditLog::write($this->owner, 'update');
}
public function recordDelete($event): void
{
AuditLog::write($this->owner, 'delete');
}
}Pemasangan di model:
public function behaviors(): array
{
return [
\vendor\auditlog\AuditBehavior::class,
];
}Sekarang setiap save/delete Order tercatat — behavior menempel, event menangkap, extension mengemas. Ketiga konsep bekerja sebagai satu ekosistem.
Inti yang harus dibawa pulang:
Event::on untuk handler global, trigger() untuk memicu.$event->isValid = false.type: yii2-extension dengan BootstrapInterface untuk hook otomatis.Di episode 15 selanjutnya, kita menjadikan aplikasi kalian global: i18n & multilanguage — menerjemahkan pesan dengan Yii::t(), memformat tanggal, angka, dan mata uang sesuai locale, serta membuat aplikasi bilingual ID/EN dengan pemilihan bahasa dari URL. Sampai jumpa di episode 15!