Property class tanpa deklarasi tipe bersedia menampung nilai apa pun: integer hari ini, string besok, array lusa. Kelonggaran tersebut menjadi pintu masuk bug klasik — property $harga yang seharusnya berisi angka ternyata berisi teks "diskon", dan perhitungan total belanja pun gagal di baris yang jauh dari sumber kesalahannya. Sejak PHP 7.4, property dapat dideklarasikan bertipe dengan sintaks yang sama seperti type declaration pada parameter dan return value function, sehingga penugasan nilai yang tidak sesuai tertangkap tepat di lokasi kejadiannya. Section Advanced Object Model kita mulai dari fondasi ini: deklarasi tipe, kondisi belum terinisialisasi yang menjadi pembeda terpenting dari property lama, koersi tipe pada mode weak dan strict, efek unset(), hingga aturan tipe pada class turunan.

Deklarasi Property dengan Tipe #

Sintaksnya sederhana: visibility, lalu tipe, lalu nama property — misalnya public string $nama. Tipe yang bisa dipakai sama dengan daftar type declaration pada umumnya: tipe primitif seperti int, float, string, bool; array; object; nama class dan interface; hingga union type seperti int|string yang sudah dibahas pada Chapter Union Types . Satu pengecualian penting: callable tidak diizinkan pada property — PHP menolaknya sejak parsing dengan pesan cannot have type callable karena callable tidak dapat diverifikasi secara konsisten pada property.

php
<?php

class Produk
{
    public string $nama;
    public int $harga;
    public float $berat;
    public bool $tersedia;
}

$produk = new Produk();
$produk->nama = "Kopi Gayo";
$produk->harga = 85000;
$produk->berat = 0.25;
$produk->tersedia = true;

echo "{$produk->nama}: Rp{$produk->harga}\n";
echo "Berat: {$produk->berat} kg\n";
echo "Stok tersedia: " . ($produk->tersedia ? "ya" : "tidak") . "\n";

Output yang Dihasilkan:

text
Kopi Gayo: Rp85000
Berat: 0.25 kg
Stok tersedia: ya

Empat deklarasi pada baris 5-8 masing-masing mengunci satu tipe. Baris 12-15 kemudian mengisi nilainya; setiap penugasan melewati pemeriksaan runtime yang memastikan nilai sesuai deklarasi — "Kopi Gayo" diterima $nama karena bertipe string, 85000 diterima $harga karena bertipe integer. Ketika seluruh nilai masuk dengan benar, keluarannya utuh: nama produk, harga, berat dalam kilogram, dan status ketersediaan stok.

Akses Sebelum Inisialisasi Berakhir Error #

Perbedaan perilaku paling penting dari property tak bertipe muncul justru sebelum nilai pertama masuk. Property tak bertipe yang tidak diberi default otomatis bernilai null dan aman dibaca kapan saja. Property bertipe tanpa default berstatus belum terinisialisasi — bukan null, bukan nol, melainkan kosong total — dan membacanya melempar Error:

php
<?php

class Produk
{
    public string $nama;
}

$produk = new Produk();
echo "Sebelum membaca property\n";
echo $produk->nama;

Output yang Dihasilkan:

text
Sebelum membaca property

Fatal error: Uncaught Error: Typed property Produk::$nama must not be accessed before initialization in /home/z/my-project/php-course/content/php/advanced-object-model/typed-properties/.verify_tmp.php:10
Stack trace:
#0 {main}
  thrown in /home/z/my-project/php-course/content/php/advanced-object-model/typed-properties/.verify_tmp.php on line 10

Baris echo pertama sempat mencetak pesannya; baris terakhir kemudian melempar Fatal error: Uncaught Error: Typed property Produk::$nama must not be accessed before initialization dan eksekusi berhenti dengan exit code 255. Kesalahan ini terjadi saat runtime, tepat di baris pembacaan, sehingga penelusuran penyebabnya tidak pernah jauh. Tiga jalan keluarnya: inisialisasi di constructor, beri nilai default, atau deklarasikan nullable dengan default null — dua yang terakhir kita bahas berikutnya.

Property Nullable dan Jebakan Nilai Default #

Deklarasi ?string menyatakan bahwa property boleh berisi string atau null. Dengan default null yang eksplisit, property langsung aman dibaca sejak object diciptakan:

php
<?php

class Pelanggan
{
    public ?string $namaPanggilan = null;
}

$pelanggan = new Pelanggan();
var_dump($pelanggan->namaPanggilan);

$pelanggan->namaPanggilan = "Budi";
var_dump($pelanggan->namaPanggilan);

Output yang Dihasilkan:

text
NULL
string(4) "Budi"

Keluaran pertama berupa NULL membuktikan property sudah terinisialisasi sejak awal, berkat = null pada baris 5. Setelah diisi "Budi", var_dump menampilkan string(4) "Budi". Membaca property nullable yang sah bernilai null tidak pernah memicu error karena null memang termasuk nilai yang diizinkan.

Koersi Tipe pada Mode Weak #

Secara bawaan — tanpa declare(strict_types=1) — PHP berjalan pada mode weak dan mengonversi nilai secara otomatis sebelum memeriksanya, mengikuti aturan type juggling yang sama dengan koersi parameter function pada Chapter Type Juggling . String numerik diterima property int, dan integer diterima property string:

php
<?php

class Keranjang
{
    public int $jumlah;
    public string $label;
}

$keranjang = new Keranjang();
$keranjang->jumlah = "5";
$keranjang->label = 12345;

var_dump($keranjang->jumlah);
var_dump($keranjang->label);

Output yang Dihasilkan:

text
int(5)
string(5) "12345"

Penugasan "5" pada baris 10 dikoersi menjadi integer 5, dan penugasan 12345 pada baris 11 menjadi string "12345" — keduanya terbukti dari keluaran var_dump. Koersi ini menolong data dari formulir dan API yang hampir selalu tiba sebagai string. Namun ada satu kasus yang sengaja dibuat bising:

php
<?php

class Berat
{
    public int $gram;
}

$berat = new Berat();
$berat->gram = 1.5;

var_dump($berat->gram);

Output yang Dihasilkan:

text

Deprecated: Implicit conversion from float 1.5 to int loses precision in /home/z/my-project/php-course/content/php/advanced-object-model/typed-properties/.verify_tmp.php on line 9
int(1)

Konversi 1.5 ke int memotong pecahan, sehingga PHP memancarkan Deprecated: Implicit conversion from float 1.5 to int loses precision sebelum menyimpan int(1). Peringatan ini adalah teman Anda: ia menandai titik yang kemungkinan besar memuat bug pembulatan. Bila nilai pecahan memang sah untuk data tersebut, deklarasikan float dan bulatkan secara sadar dengan round() atau cast (int) di titik yang Anda tentukan sendiri.

Mode Strict Menolak Konversi Otomatis #

Berkas yang diawali declare(strict_types=1) mematikan seluruh koersi — nilai harus sudah bertipe tepat sebelum ditugaskan. Aturannya berlaku per-berkas dan harus ditulis sebagai statement pertama, sebagaimana dibahas pada Chapter Strict Types dalam Fungsi :

php
<?php

declare(strict_types=1);

class Keranjang
{
    public int $jumlah;
}

$keranjang = new Keranjang();
$keranjang->jumlah = "5";

Output yang Dihasilkan:

text

Fatal error: Uncaught TypeError: Cannot assign string to property Keranjang::$jumlah of type int in /home/z/my-project/php-course/content/php/advanced-object-model/typed-properties/.verify_tmp.php:11
Stack trace:
#0 {main}
  thrown in /home/z/my-project/php-course/content/php/advanced-object-model/typed-properties/.verify_tmp.php on line 11

Penugasan yang persis sama dengan contoh mode weak kini berakhir Fatal error: Uncaught TypeError: Cannot assign string to property Keranjang::$jumlah of type int. Mode strict memindahkan seluruh tanggung jawab konversi ke kode Anda — biasanya di tepi sistem saat mem-parsing masukan — sementara jeroan aplikasi menikmati jaminan tipe penuh. Tim besar umumnya memilih strict agar ketidaksesuaian tipe tertangkap lebih awal.

unset Mengembalikan Property ke Kondisi Awal #

Fungsi unset() pada property bertipe tidak mengosongkan nilainya menjadi null, melainkan mengembalikannya ke status belum terinisialisasi — persis kondisi property yang baru dideklarasikan tanpa default:

php
<?php

class Sesi
{
    public ?string $pengguna = "Budi";
}

$sesi = new Sesi();
echo "Sebelum unset: {$sesi->pengguna}\n";

unset($sesi->pengguna);
var_dump(isset($sesi->pengguna));

$sesi->pengguna = "Siti";
echo "Setelah diisi ulang: {$sesi->pengguna}\n";

Output yang Dihasilkan:

text
Sebelum unset: Budi
bool(false)
Setelah diisi ulang: Siti

Baris 11 melepas nilai "Budi", dan isset() pada baris 12 mengembalikan bool(false) — isset() memang menguji “sudah ter-set dan bukan null”, dan property yang belum terinisialisasi jelas belum ter-set. Membaca $sesi->pengguna langsung pada titik itu akan melempar Error yang sama seperti contoh kedua; baris 14 menunjukkan property tersebut dapat diisi ulang secara normal setelahnya. Pola isset($objek->properti) inilah cara baku menguji property opsional tanpa memicu error.

Tipe Property pada Warisan #

Aturan terakhir menyangkut warisan: tipe property bersifat invarian — class turunan yang mendeklarasikan ulang property wajib memakai tipe yang persis sama dengan milik parent. Berbeda dengan parameter function yang boleh diperluas (widening) atau return value yang boleh dipersempit, property tidak diberi kelonggaran karena property terbaca dan tertulis dari dua arah sekaligus:

php
<?php

class Dasar
{
    public int $nilai = 0;
}

class Turunan extends Dasar
{
    public int|string $nilai;
}

Output yang Dihasilkan:

text

Fatal error: Type of Turunan::$nilai must be int (as in class Dasar) in /home/z/my-project/php-course/content/php/advanced-object-model/typed-properties/.verify_tmp.php on line 8

PHP menolak seluruh berkas saat kompilasi dengan Fatal error: Type of Turunan::$nilai must be int (as in class Dasar) — bahkan sebelum satu baris pun dieksekusi. Apabila sebuah nilai memang wajar memiliki lebih dari satu tipe, deklarasikan union type sejak class dasarnya. Mendeklarasikan ulang property dengan tipe yang identik diperbolehkan, meskipun jarang berguna; yang bisa disesuaikan hanya nilai default-nya.

Ringkasan #

Typed properties memberi property mekanisme pertahanan diri: tipe diperiksa pada setiap penugasan, akses sebelum inisialisasi gagal keras, unset() mengembalikan kondisi awal, dan warisan menjaga tipe tetap identik. Mode weak tetap mengizinkan koersi yang nyaman — dengan peringatan untuk konversi yang kehilangan presisi — sedangkan mode strict menuntut ketepatan penuh. Tipe menjaga bentuk data; chapter berikutnya menambahkan pengunci isi: readonly memastikan nilai yang sudah masuk tidak bisa diganti lagi sepanjang umur object.

Referensi #