Menyimpan baris baru ke database di Eloquent ternyata bukan satu jalan tunggal. Ada pola satu langkah lewat create(), pola dua langkah lewat new plus save(), pola “cari atau buat” lewat firstOrCreate(), sampai pola “cari atau perbarui” lewat updateOrCreate(). Masing-masing punya waktu terbaiknya: create() untuk data form yang sudah tervalidasi, dua langkah saat nilai harus dihitung dulu, dan dua method terakhir untuk kode yang harus aman dijalankan berulang. Chapter ini menguji keempatnya di atas data uji Budi dan Siti, lalu menutup dengan utilitas penghitung increment() serta alat introspeksi wasChanged() dan fresh().

create(): Satu Panggilan, Satu Baris #

Method create() menerima array atribut, menyimpannya lewat mekanisme mass assignment yang kita amankan di chapter Mass Assignment , lalu mengembalikan instance model yang sudah tersimpan:

php
use App\Models\Post;

$post = Post::create([
    'user_id' => 1,
    'judul' => 'Tulisan Pertama Budi',
    'slug' => 'tulisan-pertama-budi',
    'isi' => 'Lima kolom terisi lewat satu panggilan create().',
    'published' => true,
]);

echo 'create(): id ', $post->id,
    ' | wasRecentlyCreated: ', var_export($post->wasRecentlyCreated, true), "\n";
echo 'total post sekarang: ', Post::count(), "\n";

Output yang Dihasilkan:

text
create(): id 6 | wasRecentlyCreated: true
total post sekarang: 6

Setelah save() internal selesai, model kembali membawa id dari database — post keenam sekaligus id tertinggi. Property wasRecentlyCreated juga terisi true, menandakan baris ini baru saja di-INSERT, bukan hasil pencarian. Kolom views tidak kita kirim, sehingga nilainya mengikuti default kolom, yaitu 0.

Dua Langkah: new, Atribut, save() #

Pola kedua memisahkan pembuatan object dan penyimpanannya:

php
$post = new Post;
$post->user_id = 1;
$post->judul = 'Catatan Kaki';
$post->slug = 'catatan-kaki';
$post->isi = 'Diisi atribut demi atribut lalu disimpan.';
$post->published = false;
$post->save();

echo 'setelah save(): id ', $post->id,
    ' | exists: ', var_export($post->exists, true),
    ' | total post: ', Post::count(), "\n";

Output yang Dihasilkan:

text
setelah save(): id 7 | exists: true | total post: 7

Penugasan per atribut tidak tunduk pada fillable — itu hanya berlaku untuk mass assignment — sehingga pola ini cocok saat nilai perlu dihitung, di-format, atau diambil dari sumber berbeda-beda. Property exists berpindah dari false menjadi true begitu save() mengeksekusi INSERT, dan id baru lahir bersamaan dengan penyimpanan.

firstOrCreate: Cari, Kalau Tidak Ada Buat #

Method firstOrCreate() menerima dua array: kolom pencarian, lalu nilai yang dipakai kalau baris harus dibuat. Ia membuat kode idempoten — aman diulang berapa pun kali tanpa menduplikasi data:

php
$ada = Post::firstOrCreate(
    ['slug' => 'belajar-eloquent'],
    ['user_id' => 1, 'judul' => 'Belajar Eloquent', 'isi' => 'Sudah ada sejak awal.'],
);
echo 'firstOrCreate (slug sudah ada): id ', $ada->id,
    ' | wasRecentlyCreated: ', var_export($ada->wasRecentlyCreated, true), "\n";

$baru = Post::firstOrCreate(
    ['slug' => 'glosarium-orm'],
    ['user_id' => 2, 'judul' => 'Glosarium ORM', 'isi' => 'Lahir lewat firstOrCreate.'],
);
echo 'firstOrCreate (slug baru): id ', $baru->id,
    ' | wasRecentlyCreated: ', var_export($baru->wasRecentlyCreated, true), "\n";

Output yang Dihasilkan:

text
firstOrCreate (slug sudah ada): id 1 | wasRecentlyCreated: false
firstOrCreate (slug baru): id 8 | wasRecentlyCreated: true

Pemanggilan pertama menemukan post ber-slug belajar-eloquent sudah ada sejak data uji, sehingga tidak ada query INSERT dan wasRecentlyCreated bernilai false. Pemanggilan kedua tidak menemukan slug glosarium-orm, jadi Eloquent membuat baris baru ber-id 8. Property wasRecentlyCreated kembali menjadi alat pembeda yang jujur antara “ketemu” dan “baru dibuat”.

updateOrCreate: Cari atau Perbarui #

Kakak dari firstOrCreate() bekerja sebaliknya saat baris ditemukan: atribut kedua menjadi pembaruan, bukan diabaikan:

php
$diubah = Post::updateOrCreate(
    ['slug' => 'tips-seeding-data'],
    ['judul' => 'Tips Seeding Data (Revisi)', 'published' => true],
);
echo 'updateOrCreate: id ', $diubah->id, ' | judul: ', $diubah->judul, "\n";
echo 'total post sekarang: ', Post::count(), "\n";

Output yang Dihasilkan:

text
updateOrCreate: id 4 | judul: Tips Seeding Data (Revisi)
total post sekarang: 8

Post milik Siti ber-slug tips-seeding-data ditemukan, lalu judulnya ditimpa dan statusnya di-publish — total baris tetap delapan karena tidak ada INSERT. Method seperti ini adalah tulang punggung seeder dan sinkronisasi data dari API, tempat kode harus “buat kalau belum ada, mutakhirkan kalau sudah”.

increment() dan decrement() #

Untuk kolom angka, Eloquent menawarkan jalur yang jauh lebih murah daripada baca-modifikasi-tulis: increment() dan decrement() mengeksekusi satu query UPDATE ... SET views = views + n tanpa memuat barisnya:

php
Post::where('id', 3)->increment('views');
echo 'views post 3 setelah increment: ', Post::find(3)->views, "\n";

Post::find(3)->decrement('views', 10);
echo 'views post 3 setelah decrement 10: ', Post::find(3)->views, "\n";

Output yang Dihasilkan:

text
views post 3 setelah increment: 281
views post 3 setelah decrement 10: 271

Views post ketiga naik dari 280 ke 281 lewat satu UPDATE, lalu turun 10 menjadi 271. Perhatikan dua gaya pemanggilannya: increment() bisa dipanggil dari query builder untuk banyak baris sekaligus, atau dari instance model untuk satu baris spesifik.

wasChanged(), fresh(), dan touch() #

Setelah menyimpan, model tahu persis apa yang baru saja terjadi padanya. Method wasChanged() melaporkan apakah ada atribut yang berubah pada save() terakhir, dan getChanges() merinci kolomnya:

php
$post = Post::find(1);
$post->judul = 'Belajar Eloquent (Edisi Kedua)';
$post->save();

echo 'wasChanged: ', var_export($post->wasChanged(), true),
    ' | kolom: ', implode(', ', array_keys($post->getChanges())), "\n";

Output yang Dihasilkan:

text
wasChanged: true | kolom: judul, updated_at

Dua kolom berubah: judul yang kita edit, dan updated_at yang digeser Eloquent secara otomatis. Sebaliknya, fresh() mengambil ulang baris dari database tanpa menyimpan perubahan di memori:

php
$post = Post::find(2);
$post->judul = 'Judul yang hanya hidup di memori';

$asli = $post->fresh();
echo 'di memori: ', $post->judul, "\n";
echo 'di database: ', $asli->judul, "\n";

Output yang Dihasilkan:

text
di memori: Judul yang hanya hidup di memori
di database: Rahasia Query Builder

Perubahan judul hanya hidup di object $post dan lenyap begitu $asli = $post->fresh() membaca kondisi sebenarnya dari database. Terakhir, ada touch() — penyimpanan spesial yang hanya memperbarui updated_at tanpa menyentuh kolom lain, berguna misalnya menandai post “pernah dilihat” tanpa mengubah isinya:

php
$post = Post::find(5);
$post->touch();
echo 'touch() menyentuh updated_at saja: ',
    var_export($post->wasChanged('updated_at'), true), "\n";

Output yang Dihasilkan:

text
touch() menyentuh updated_at saja: true

Empat pola penyimpanan plus utilitas penghitung ini menutup kisah pembuatan data. Namun updateOrCreate tadi menyisipkan satu hal yang belum kita bahas tuntas: bagaimana memperbarui baris yang sudah ada secara penuh — dari save() pada model hasil query sampai fill() dan pembaruan massal. Itulah isi chapter berikutnya, Memperbarui Data.