Class OOP klasik di PHP menulis data yang sama tiga kali: sekali sebagai deklarasi property, sekali sebagai parameter constructor, dan sekali sebagai assignment di badan constructor. Untuk satu property, pengulangan ini masih bisa ditoleransi; untuk class dengan delapan property, kita menulis dua puluh empat potongan kode yang sebagian besar hanya menyalin nama dan tipe yang sama. PHP 8.0 memangkas ritual tersebut lewat constructor property promotion: satu baris parameter constructor yang sekaligus mendeklarasikan property dan mengisinya. Fitur ini menjadi salah satu fitur modern PHP yang paling cepat diadopsi karena diterapkan begitu saja pada pola yang sudah kita tulis selama bertahun-tahun.

Boilerplate Constructor Sebelum PHP 8.0 #

Mari kita lihat dulu bentuk lama yang ingin digantikan. Class produk berikut menyimpan tiga data: nama, harga, dan stok.

php
<?php

class ProdukLama
{
    public string $nama;
    public float $harga;
    public int $stok;

    public function __construct(string $nama, float $harga, int $stok)
    {
        $this->nama = $nama;
        $this->harga = $harga;
        $this->stok = $stok;
    }
}

$produk = new ProdukLama("Kopi Gayo", 65000.0, 12);
echo "{$produk->nama}: Rp{$produk->harga} (stok {$produk->stok})\n";

Output yang Dihasilkan:

text
Kopi Gayo: Rp65000 (stok 12)

Perhatikan pengulangan yang terjadi:

  1. Baris 5-7: tiga property dideklarasikan lengkap dengan tipe datanya.
  2. Baris 9: constructor menerima tiga parameter dengan nama dan tipe yang sama persis.
  3. Baris 11-13: badan constructor hanya menyalin setiap parameter ke property senamanya.

Total sembilan baris hanya untuk menghubungkan data yang sama. Setiap property baru menambah tiga baris di tiga lokasi berbeda, dan salah urutan assignment seperti $this->stok = $harga tidak akan pernah ditangkap pemeriksa sintaks.

Sintaks Constructor Property Promotion #

Sekarang versi PHP 8.0 ke atas. Letakkan modifier visibility di depan parameter constructor, dan PHP otomatis melakukan ketiga pekerjaan tersebut:

php
<?php

class Produk
{
    public function __construct(
        public string $nama,
        public float $harga,
        public int $stok,
    ) {}
}

$produk = new Produk("Kopi Gayo", 65000.0, 12);
echo "{$produk->nama}: Rp{$produk->harga} (stok {$produk->stok})\n";

Output yang Dihasilkan:

text
Kopi Gayo: Rp65000 (stok 12)

Anatomi satu parameter terpromosi dapat dibedah menjadi tiga bagian:

php
<?php

class Produk
{
    public function __construct(
        public string $nama,
        protected float $harga,
        private int $stok,
    ) {}
}
BagianPeran
public / protected / privateVisibility property sekaligus penanda bahwa parameter ini dipromosikan
stringTipe property, sekaligus tipe parameter constructor
$namaNama property, sekaligus nama parameter constructor

Ketika konstruktor berjalan, PHP otomatis mengeksekusi $this->nama = $nama untuk setiap parameter terpromosi — persis seperti badan constructor versi lama. Modifier visibility adalah pemicu promotion: parameter tanpa modifier tetap menjadi parameter biasa yang tidak pernah berubah menjadi property. Karena itu, kelas di atas boleh dipakai modul mana pun untuk $nama, sementara $harga hanya boleh diakses turunannya dan $stok hanya dari dalam class Produk sendiri.

Mencampur Parameter Terpromosi dan Parameter Biasa #

Promotion bukan pilihan serba atau serba tidak — kedua gaya parameter boleh dicampur dalam satu constructor. Pola ini berguna ketika sebagian data layak menjadi property sedangkan sebagian lagi hanya input sementara untuk logika konstruktor:

php
<?php

class Pesanan
{
    public string $status;

    public function __construct(
        public string $nomor,
        public readonly DateTimeImmutable $dibuatPada,
        string $statusAwal = "menunggu",
    ) {
        $this->status = $statusAwal;
    }
}

$pesanan = new Pesanan("INV-2026-001", new DateTimeImmutable("2026-01-15 08:00:00"));
echo $pesanan->nomor . "\n";
echo $pesanan->dibuatPada->format("Y-m-d H:i:s") . "\n";
echo $pesanan->status . "\n";

Output yang Dihasilkan:

text
INV-2026-001
2026-01-15 08:00:00
menunggu

Mari kita bedah campuran di atas:

  1. Baris 5: property $status dideklarasikan manual karena parameternya memang tidak ingin dipromosikan.
  2. Baris 8 (public string $nomor): parameter terpromosi biasa — menjadi property public yang bisa ditulis ulang.
  3. Baris 9 (public readonly DateTimeImmutable $dibuatPada): promotion dipadukan readonly (fitur PHP 8.1), sehingga timestamp pembuatan terkunci setelah konstruktor selesai. PHP juga menerima readonly tanpa visibility pada parameter terpromosi — hasilnya sama dengan public readonly — tetapi menulis public readonly lebih eksplisit dan lebih lazim dijumpai.
  4. Baris 10 (string $statusAwal = "menunggu"): parameter biasa tanpa modifier, lengkap dengan nilai default; nilai ini tidak otomatis mengisi property apa pun.
  5. Baris 12: badan constructor tetap dieksekusi setelah semua assignment otomatis — $this->status = $statusAwal adalah jembatan manual dari parameter biasa ke property yang dideklarasikan manual.

Ada satu batasan tipe yang perlu diingat: callable tidak boleh menjadi tipe property, sehingga parameter terpromosi bertipe callable langsung ditolak PHP:

php
<?php

class ContohCallable
{
    public function __construct(public callable $proses) {}
}

Output yang Dihasilkan:

text

Fatal error: Property ContohCallable::$proses cannot have type callable in /home/z/my-project/php-course/content/php/advanced-object-model/constructor-property-promotion/.verify_tmp.php on line 5

Pesan Property ContohCallable::$proses cannot have type callable menegaskan aturan tersebut. Jika Anda perlu menyimpan callback, gunakan tipe Closure atau interface lain yang mendeskripsikan kontrak dengan lebih jelas.

Promotion Bertemu readonly Class #

Bab Readonly Classes memperlihatkan bahwa DTO modern PHP cukup ditulis dalam satu blok deklarasi. Itu hanya mungkin karena dua fitur ini bekerja sama: readonly class mengunci, promotion mendeklarasikan dan mengisi:

php
<?php

readonly class DataPengguna
{
    public function __construct(
        public string $nama,
        public string $email,
        public array $peran,
    ) {}
}

$pengguna = new DataPengguna("Budi", "[email protected]", ["member"]);
echo $pengguna->nama . " <" . $pengguna->email . ">\n";
echo implode(", ", $pengguna->peran) . "\n";

Output yang Dihasilkan:

text
Budi <[email protected]>
member

Tujuh baris deklarasi menghasilkan DTO dengan tiga property terkunci yang terisi otomatis — bandingkan dengan versi lama yang membutuhkan deklarasi property, daftar parameter, dan blok assignment terpisah.

Ringkasan #

Constructor property promotion menggabungkan deklarasi property, parameter constructor, dan assignment menjadi satu baris bertanda visibility. Parameter terpromosi dan parameter biasa bisa dicampur sesuai kebutuhan, nilai default parameter ikut menjadi nilai awal property, dan callable menjadi satu-satunya tipe yang dilarang. Dipadukan dengan readonly class, fitur ini menghasilkan DTO yang ringkas sekaligus terkunci. Hingga titik ini property kita masih polos: ditulis begitu saja, tersimpan begitu saja. PHP 8.4 memungkinkan logika get dan set menempel langsung pada property melalui property hooks, topik chapter berikutnya.

Referensi #