Chapter Return Types pada Section Fungsi membahas mekanikanya: cara mendeklarasikan, koersi nilai kembali, dan perbedaan void dari fungsi tanpa return type. Bab ini melanjutkan dari sudut pandang sistem tipe: apa yang terjadi pada kontrak keluaran ketika method diwariskan, diimplementasikan, atau ditimpa. Ternyata arah aturannya persis kebalikan dari parameter — dan memahami simetri inilah kunci merancang hierarki class yang sehat.

Covariance — Anak Boleh Mempersempit Return Type #

Sejak PHP 7.4, method anak boleh mempersempit (narrowing) return type ke subtype-nya; aturan arah ini disebut covariance. Keamanannya mudah dilihat dari posisi pemanggil: siapa pun yang memanggil buat() dan menyiapkan diri menerima Bentuk pasti aman menerima Persegi, karena Persegi adalah Bentuk dengan seluruh perilakunya.

php
<?php
abstract class Bentuk
{
    abstract public function nama(): string;
}

class Persegi extends Bentuk
{
    public function nama(): string { return "Persegi"; }
}

abstract class PabrikBentuk
{
    abstract public function buat(): Bentuk;
}

class PabrikPersegi extends PabrikBentuk
{
    // Mempersempit return type Bentuk → Persegi (covariance, sah sejak PHP 7.4)
    public function buat(): Persegi
    {
        return new Persegi();
    }
}

$pabrik = new PabrikPersegi();
$bentuk = $pabrik->buat();
echo $bentuk->nama() . "\n";

Output yang Dihasilkan:

text
Persegi

Mari kita bedah kontrak yang terbentuk:

  1. Kontrak umum — PabrikBentuk::buat(): Bentuk menjanjikan keluaran bertipe Bentuk; kode generik yang mengandalkan pabrik ini hanya berharap sejauh itu.
  2. Penyempitan di anak — PabrikPersegi::buat(): Persegi menjanjikan lebih tajam; pemanggil yang tahu sedang memakai pabrik persegi bisa langsung memanggil method khas Persegi tanpa pengecekan tambahan.
  3. Aturan yang sama untuk interface — covariance berlaku identik saat mengimplementasikan interface, bukan hanya saat menimpa method konkret dari class abstrak.

Arah Perubahan Return Type #

Perubahan return type di kelas anakStatus
Tetap sama persis dengan parentSah
Bentuk → Persegi (menyempitkan)Sah — covariance
Persegi → Bentuk (melebarkan)Fatal error
Menghapus return typeFatal error
Tidak cocok dengan tentative type bawaanDeprecated (dapat ditekan sementara)

Melebarkan return type membuat janji melemah: pemanggil yang mengandalkan Persegi mendapat Bentuk dan kehilangan jaminan yang dipakainya. PHP menolaknya dengan fatal error — bukan peringatan — pada saat deklarasi class diproses, sama seperti kasus penyempitan parameter pada chapter Parameter Types . Hanya baris terakhir tabel yang mendapat perlakuan lunak, dan itulah pokok bahasan bagian khusus di bagian bawah chapter ini.

self, static, dan parent sebagai Return Type #

PHP menyediakan tiga kata kunci khusus untuk return type yang merujuk ke hierarki class itu sendiri. self selalu menunjuk kelas tempat deklarasi ditulis; static mengikuti kelas objek yang memanggil (memanfaatkan late static binding, tersedia sebagai return type sejak PHP 8.0); parent menunjuk kelas induk langsung dan lazim pada method yang membangun instansi serupa. Menuliskan nama class secara literal (: Kue) juga sah dan paling eksplisit:

php
<?php
class Kue
{
    public function __construct(public string $rasa = "cokelat") {}

    // self: selalu menunjuk kelas tempat deklarasi berada
    public function salin(): self
    {
        return new self($this->rasa);
    }

    // static: mengikuti kelas objek yang sedang memanggil
    public function segarkan(): static
    {
        return new static($this->rasa);
    }
}

class KueUltah extends Kue {}

$kue = new KueUltah("keju");
echo get_class($kue->salin()) . "\n";
echo get_class($kue->segarkan()) . "\n";

Output yang Dihasilkan:

text
Kue
KueUltah

Perbedaan kedua kata kunci tersebut terlihat langsung dari keluarannya:

  1. self terkunci pada kelas pendeklarasi — salin() dideklarasikan di Kue, sehingga new self selalu membangun Kue sekalipun dipanggil dari objek KueUltah; get_class() melaporkan Kue.
  2. static mengikuti objek — segarkan() dipanggil dari objek KueUltah, dan new static membangun KueUltah; get_class() melaporkan KueUltah.
  3. Pola pustaka modern — kata kunci static inilah yang membuat fluent interface dan method gaya withX() tetap mengembalikan instansi kelas anak, sehingga rantai pemanggilan tidak kehilangan tipe aslinya.

never dan void dalam Hierarki #

Dua return type istimewa punya aturan warisan tersendiri. void menyatakan “tidak ada nilai bermakna yang dikembalikan” — pada praktiknya anak wajib tetap void. never menyatakan “fungsi tidak pernah kembali” karena selalu melempar exception atau menghentikan eksekusi; karena never merupakan subtype dari semua tipe, anak boleh mempersempit return type apa pun menjadi never. Dasar keduanya dibahas pada chapter Never dan Void , sedangkan penggunaan mendalamnya ada di Never Return Type .

php
<?php
function gagal(string $pesan): never
{
    throw new RuntimeException($pesan);
}

try {
    gagal("Stok habis");
} catch (RuntimeException $e) {
    echo "Ditangkap: " . $e->getMessage() . "\n";
}

Output yang Dihasilkan:

text
Ditangkap: Stok habis

Apa yang terjadi pada contoh tersebut:

  1. Kontrak never — gagal() hanya punya satu jalur: melempar RuntimeException; return type never menyatakan bahwa tidak ada pemanggil yang akan pernah menerima nilai kembali.
  2. Penangkapan — blok try-catch menangkap exception tersebut dan mencetak pesannya; pemanggil bertanggung jawab menangani jalur kegagalan ini.
  3. Manfaat bagi pembaca kode — deklarasi never memberi tahu manusia dan static analysis bahwa kode setelah pemanggilan gagal() tidak akan pernah tercapai.

Tentative Return Types dan #[\ReturnTypeWillChange] #

PHP 8.1 menambahkan return type pada banyak method internal — ArrayAccess::offsetGet(): mixed, Iterator::current(): mixed, dan kawan-kawannya — secara tentative: tidak langsung wajib, tetapi ditandai sebagai arah tujuan. Method Anda yang tidak cocok tidak fatal, melainkan menerbitkan deprecation berisi saran perbaikan. Mari kita lihat wujudnya pada implementasi ArrayAccess tanpa return type:

php
<?php
class KeranjangLama implements ArrayAccess
{
    private array $isi = [];

    public function offsetExists(mixed $kunci): bool
    {
        return isset($this->isi[$kunci]);
    }

    // Tanpa return type: PHP 8.1+ menerbitkan deprecation (tentative return type)
    public function offsetGet(mixed $kunci)
    {
        return $this->isi[$kunci] ?? null;
    }

    public function offsetSet(mixed $kunci, mixed $nilai): void
    {
        $this->isi[$kunci] = $nilai;
    }

    public function offsetUnset(mixed $kunci): void
    {
        unset($this->isi[$kunci]);
    }
}

$keranjang = new KeranjangLama();
$keranjang["kopi"] = "Gayo";
echo $keranjang["kopi"] . "\n";

Output yang Dihasilkan:

text

Deprecated: Return type of KeranjangLama::offsetGet(mixed $kunci) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/z/my-project/php-course/content/php/type-system-dan-static-analysis/return-types/.verify_tmp.php on line 12
Gayo

Deprecation tercetak begitu class dihubungkan — sebelum baris echo terakhir berjalan — lalu program tetap dilanjutkan. Pesannya lengkap: signature yang diharapkan (ArrayAccess::offsetGet(mixed $offset): mixed) disandingkan dengan atribut yang bisa dipakai untuk menunda, disertai nama berkas dan nomor baris (nama berkas .verify_tmp.php pada keluaran di atas adalah berkas sementara milik alat verifikasi otomatis buku ini). Dua jalan keluar tersedia. Perbaikan yang sesungguhnya adalah menyamakan signature — tambahkan : mixed pada offsetGet(). Bila kode lama belum siap diubah, atribut #[\ReturnTypeWillChange] menekan deprecation untuk sementara:

php
<?php
class KeranjangLegacy implements ArrayAccess
{
    private array $isi = [];

    public function offsetExists(mixed $kunci): bool
    {
        return isset($this->isi[$kunci]);
    }

    #[\ReturnTypeWillChange] // menekan deprecation untuk sementara
    public function offsetGet(mixed $kunci)
    {
        return $this->isi[$kunci] ?? null;
    }

    public function offsetSet(mixed $kunci, mixed $nilai): void
    {
        $this->isi[$kunci] = $nilai;
    }

    public function offsetUnset(mixed $kunci): void
    {
        unset($this->isi[$kunci]);
    }
}

$keranjang = new KeranjangLegacy();
$keranjang["kopi"] = "Gayo";
echo $keranjang["kopi"] . "\n";

Output yang Dihasilkan:

text
Gayo

Keluaran kini bersih tanpa deprecation — tetapi hanya bersih di permukaan; kontrak belum dipenuhi dan tetap tercatat sebagai utang upgrade. Perbedaan perilaku antara kedua kelas di atas hanyalah kehadiran atribut pada offsetGet(); isi logika keduanya identik.

Referensi #

Ringkasan #

Return type dibaca sebagai janji keluaran: anak boleh mempersempitnya (covariance, sah sejak PHP 7.4) tetapi tidak boleh melebarkannya, dan pelanggaran arah ditolak fatal sejak deklarasi class. Kata kunci self, static, dan parent menjaga identitas kelas di sepanjang hierarki, never menyempit kontrak hingga titik “tidak pernah kembali”, sementara tentative return types memberi jalan transisi yang lunas bagi method bawaan. Aturan variance untuk parameter dan return kini lengkap di tangan Anda; chapter Variance merangkum keduanya dalam satu kerangka, dan Property Types melanjutkan kontrak ke lokasi deklarasi berikutnya: property.