Lompat ke konten
dokumentasi.id
Commit 159d6ac
v1
Star1

7

Mode Morph dan Tipe Kolom ID

Bentuk kolom userstamps ditentukan oleh dua opsi konfigurasi yang saling lepas. is_using_morph memutuskan apakah kolom memakai pasangan _id + _type atau hanya _id dengan foreign key, sedangkan users_table_id_column_type memutuskan tipe kolom _id. Keduanya dibaca di satu titik, UserstampsMacro::addUserIdColumn, yang menerima Blueprint dan nama kolom lalu memilih bentuk kolom lewat match (src/Database/Schema/Macros/UserstampsMacro.php:17-22). Opsi lain yang ikut terpakai di fungsi itu — users_table dan users_table_id_column_name — dibahas di Referensi Opsi Konfigurasi, sementara perakitan macro-nya ada di Lapisan Schema dan Macro Blueprint.

Cabang konfigurasi ke bentuk kolom

graph TD
  CFG["config: is_using_morph"]
  TYPE["config: users_table_id_column_type"]
  CFG -->|"true"| MORPH["Mode morph"]
  CFG -->|"false"| FKON["Mode foreign key"]
  MORPH --> MID["kolom _id: nullable, tanpa foreign key"]
  MORPH --> MTYPE["kolom _type: string nullable"]
  FKON --> FID["kolom _id: foreign key dengan nullOnDelete"]
  TYPE --> MID
  TYPE --> FID
  MID --> SHAPEM["unsignedBigInteger / uuid / ulid / unsignedInteger"]
  FID --> SHAPEF["foreignId / foreignUuid / foreignUlid / unsignedInteger"]
  style CFG fill:#f59e0b,color:#000
  style TYPE fill:#f59e0b,color:#000
  style MORPH fill:#2d7d46,color:#fff
  style MID fill:#2d7d46,color:#fff
  style MTYPE fill:#8b5cf6,color:#fff
  style FKON fill:#336791,color:#fff
  style FID fill:#336791,color:#fff
%% fig: Cabang konfigurasi ke bentuk kolom userstamps
%% desc: is_using_morph menentukan ada tidaknya kolom _type dan foreign key, sedangkan users_table_id_column_type menentukan tipe kolom _id pada kedua mode.
Fig. 1: Cabang konfigurasi ke bentuk kolom userstampsis_using_morph menentukan ada tidaknya kolom _type dan foreign key, sedangkan users_table_id_column_type menentukan tipe kolom _id pada kedua mode.

Macro userstamps() memanggil addUserIdColumn untuk <created_by_column>_id dan <updated_by_column>_id. Kolom _type hanya ditambahkan ketika is_using_morph bernilai benar, dan tipenya selalu string(...)->nullable() tanpa mengikuti users_table_id_column_type (src/Database/Schema/Macros/UserstampsMacro.php:52-69). softUserstamps() menambahkan <deleted_by_column>_id dengan aturan yang sama, ditambah <deleted_by_column>_type bila mode morph aktif (src/Database/Schema/Macros/UserstampsMacro.php:74-85).

Macrois_using_morph = falseis_using_morph = true
userstamps()created_by_id, updated_by_id; masing-masing dengan foreign(...)->nullOnDelete()kolom yang sama ditambah created_by_type, updated_by_type; tanpa foreign key
softUserstamps()deleted_by_id dengan foreign keydeleted_by_id dan deleted_by_type; tanpa foreign key
dropUserstamps()dropForeign tiap kolom lalu dropColumndropColumn atas _id dan _type
dropSoftUserstamps()dropForeign lalu dropColumndropColumn atas deleted_by_id dan deleted_by_type

⚠️ Nilai is_using_morph memengaruhi DDL, bukan hanya query. Config menyatakan opsi ini harus ditentukan sebelum migration dijalankan karena mengubah cara kolom relasi dibuat (config/userstamps.php:10-18). Mengubahnya setelah tabel ada membuat kolom _type dan foreign key tidak sinkron dengan isi tabel.

Pemetaan users_table_id_column_type

Opsi ini menerima increments, bigIncrements, uuid, dan ulid, dengan default bigIncrements (config/userstamps.php:50-60). Perhatikan bahwa percabangan di addUserIdColumn hanya mengenali bigIncrements, uuid, dan ulid; nilai increments yang disebut di komentar config jatuh ke cabang default.

Nilai opsiis_using_morph = trueis_using_morph = false
bigIncrements (default)unsignedBigInteger($column)->nullable()foreignId($column)->nullable() + foreign()
uuiduuid($column)->nullable()foreignUuid($column)->nullable() + foreign()
ulidulid($column)->nullable()foreignUlid($column)->nullable() + foreign()
nilai lain, termasuk incrementsunsignedInteger($column)->nullable()unsignedInteger($column)->nullable() + foreign()

Cabang default menulis ekspresi yang sama pada kedua mode, yaitu unsignedInteger($column)->nullable() (src/Database/Schema/Macros/UserstampsMacro.php:37-39). Blok foreign key berada di luar match, jadi cabang default tetap mendapat foreign($column) saat mode morph nonaktif (src/Database/Schema/Macros/UserstampsMacro.php:24-47).

⚠️ Salah tulis nama tipe tidak memunculkan error. Nilainya diam-diam menjadi unsignedInteger, sehingga kolom _id bisa tidak cocok dengan primary key tabel user, misalnya bigint atau char(36).

Morph versus foreign key

Saat mode morph nonaktif, paket menambahkan constraint eksplisit: foreign($column)->references($usersTableIdColumnName)->on($usersTable)->nullOnDelete() (src/Database/Schema/Macros/UserstampsMacro.php:42-47). Karena itu users_table dan users_table_id_column_name harus menunjuk tabel user yang benar; config menegaskan users_table hanya dipakai pada mode ini (config/userstamps.php:26-33).

Saat mode morph aktif, tidak ada foreign key sama sekali. Config menyatakan users_table dan users_model diabaikan karena relasi mengacu ke model yang disimpan di kolom _type (config/userstamps.php:41-48). Integritas referensi berpindah ke lapisan aplikasi, dan nilainya diisi observer: UserstampsObserver::creating menulis _id lalu _type hanya bila is_using_morph benar (src/Observers/UserstampsObserver.php:9-28). Pola yang sama dipakai pada deleting (src/Observers/UserstampsObserver.php:42-59) dan restoring, yang mengosongkan kembali <deleted_by_column>_type menjadi null (src/Observers/UserstampsObserver.php:61-78). Isi _type berasal dari get_class($user), dan _id diambil dari kolom yang disebut users_table_id_column_name (src/Observers/UserstampsObserver.php:91-107).

Pengaruh ke relasi dan drop macro

graph TD
  REL["HasUserstamps::createdBy"] -->|"is_using_morph true"| MB["morphTo dengan kolom _type dan _id"]
  REL -->|"is_using_morph false"| BB["belongsTo users_model lewat kolom _id"]
  OBS["UserstampsObserver"] -->|"is_using_morph true"| OBST["menulis _type = get_class user"]
  DROP["dropUserstamps"] -->|"is_using_morph true"| DT["drop kolom _id dan _type"]
  DROP -->|"is_using_morph false"| DF["dropForeign lalu drop kolom _id"]
  style MB fill:#2d7d46,color:#fff
  style DT fill:#2d7d46,color:#fff
  style BB fill:#336791,color:#fff
  style DF fill:#336791,color:#fff
  style OBST fill:#8b5cf6,color:#fff
  style OBS fill:#8b5cf6,color:#fff
%% fig: Efek mode morph pada relasi, observer, dan drop macro
%% desc: Mode morph mengubah jenis relasi menjadi morphTo, membuat observer menulis kolom _type, dan mengubah cara dropUserstamps melepas kolom.
Fig. 2: Efek mode morph pada relasi, observer, dan drop macroMode morph mengubah jenis relasi menjadi morphTo, membuat observer menulis kolom _type, dan mengubah cara dropUserstamps melepas kolom.

Tiga method relasi di trait memakai pola yang identik. createdBy() mengembalikan morphTo() dengan pasangan kolom <created_by_column>_type dan <created_by_column>_id bila mode morph aktif, atau belongsTo(config('userstamps.users_model'), ..., users_table_id_column_name) bila tidak (src/Concerns/HasUserstamps.php:19-32). updatedBy() mengikuti pola yang sama (src/Concerns/HasUserstamps.php:34-47). Konsekuensinya, model yang tidak memakai morph tidak dapat mencampur dua model user berbeda dalam satu kolom.

Macro drop juga bercabang pada opsi yang sama. Pada mode morph, dropUserstamps() hanya menghimpun kolom _id dan _type lalu memanggil dropColumn, karena tidak ada constraint yang perlu dilepas (src/Database/Schema/Macros/UserstampsMacro.php:92-102). Pada mode non-morph, dropForeign dijalankan lebih dulu untuk tiap kolom sebelum kolomnya dihapus, dan pola itu diulang di dropSoftUserstamps() (src/Database/Schema/Macros/UserstampsMacro.php:108-123).

⚠️ Memanggil dropUserstamps() pada tabel yang dibuat dengan mode morph aktif tidak melepas foreign key, karena memang tidak pernah dibuat. Sebaliknya, menjalankan drop pada tabel non-morph setelah is_using_morph diubah menjadi true akan meninggalkan constraint yang tidak terlepas.

Bukti di test

Test kolom membandingkan dua mode secara langsung. Dengan is_using_morph = false, tabel hasil userstamps() dan softUserstamps() hanya memuat created_by_id, updated_by_id, deleted_by_id dan tidak memuat satu pun kolom _type (tests/Feature/UserstampsColumnsTest.php:11-31). Dengan is_using_morph = true, keenam kolom muncul, termasuk created_by_type, updated_by_type, dan deleted_by_type (tests/Feature/UserstampsColumnsTest.php:33-53).

Sisi perilaku menguji kombinasi mode dengan tipe kolom ID. Mode morph diuji dengan primary key id (tests/Feature/UserstampsBehaviorTest.php:51-78), ulid (tests/Feature/UserstampsBehaviorTest.php:80-108), dan uuid (tests/Feature/UserstampsBehaviorTest.php:110-138); mode non-morph mengulang tiga kombinasi yang sama dengan users_model diisi eksplisit (tests/Feature/UserstampsBehaviorTest.php:141-169). Setiap kasus memeriksa createdBy, updatedBy, dan deletedBy lewat helper testUserstampsBehavior. Rincian menjalankan suite ada di Pengujian dan Alur CI, sedangkan pembacaan _type di runtime dibahas di Trait HasUserstamps dan Relasi User dan Observer dan Siklus Event Model.