Union types menjawab kebutuhan “salah satu dari beberapa tipe”, tetapi sebagian masalah menuntut kebalikannya: sebuah nilai yang harus memenuhi beberapa tipe sekaligus. Bayangkan fungsi yang hanya berguna untuk object yang bisa dihitung sekaligus bisa dilalui satu per satu — hanya Countable tidak cukup, hanya Iterator pun tidak cukup. Intersection types, yang hadir sejak PHP 8.1, menuliskan tuntutan tersebut dengan tanda dan &. Pada Chapter ini kita mempelajari sintaksnya, contoh nyatanya pada class bawaan PHP, larangan mencampurnya dengan union, serta bentuk khusus DNF yang melonggarkan larangan tersebut.
Sintaks A&B #
Tipe intersection ditulis dengan menyandingkan beberapa nama interface menggunakan &. Nilai yang masuk ke parameter tersebut harus mengimplementasikan seluruh interface yang disebut — memenuhi salah satu saja tetap ditolak:
<?php
interface BisaDibaca
{
public function baca(): string;
}
interface BisaDiurut
{
public function urutkan(): void;
}
class KoleksiBuku implements BisaDibaca, BisaDiurut
{
public function baca(): string
{
return "3 buku ditemukan";
}
public function urutkan(): void
{
echo "koleksi terurut\n";
}
}
function tampilkan(BisaDibaca& BisaDiurut $item): void
{
echo $item->baca(), "\n";
$item->urutkan();
}
tampilkan(new KoleksiBuku());Output yang Dihasilkan:
3 buku ditemukan
koleksi terurutMari kita bedah kode di atas. Interface BisaDibaca dan BisaDiurut masing-masing mendefinisikan satu kontrak method, dan class KoleksiBuku mengimplementasikan keduanya. Parameter BisaDibaca& BisaDiurut $item pada fungsi tampilkan() kini dapat memanggil kedua method tersebut tanpa pemeriksaan tambahan — kompiler PHP sudah menjamin keduanya tersedia. Perhatikan juga bahwa di dalam fungsi, kita bebas memanggil $item->baca() dan $item->urutkan() karena intersection menjanjikan gabungan seluruh method dari semua interface yang terlibat.
Contoh Nyata dari SPL #
Contoh di atas memakai interface buatan sendiri, tetapi PHP sudah membawa pasangan interface yang sering disandingkan. Class-class koleksi bawaan seperti ArrayIterator mengimplementasikan Countable sekaligus Iterator, sehingga cocok untuk parameter intersection:
<?php
function hitungItem(Countable&Iterator $data): int
{
return $data->count();
}
$daftar = new ArrayIterator(["a", "b", "c"]);
var_dump($daftar instanceof Countable);
var_dump($daftar instanceof Iterator);
var_dump(hitungItem($daftar));Output yang Dihasilkan:
bool(true)
bool(true)
int(3)Kedua pemeriksaan instanceof mengembalikan true, membuktikan ArrayIterator memenuhi kedua sisi intersection, dan pemanggilan hitungItem() berjalan lancar menghasilkan 3. Sekarang mari uji class tetangganya: ArrayObject mengimplementasikan Countable, tetapi bukan Iterator — class tersebut memilih jalur IteratorAggregate. Simpan berikut sebagai hitung.php:
<?php
function hitungItem(Countable&Iterator $data): int
{
return $data->count();
}
var_dump(hitungItem(new ArrayObject(["x", "y"])));Output yang Dihasilkan:
Fatal error: Uncaught TypeError: hitungItem(): Argument #1 ($data) must be of type Countable&Iterator, ArrayObject given, called in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/intersection-types/.verify_tmp.php on line 7 and defined in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/intersection-types/.verify_tmp.php:2
Stack trace:
#0 /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/intersection-types/.verify_tmp.php(7): hitungItem(Object(ArrayObject))
#1 {main}
thrown in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/intersection-types/.verify_tmp.php on line 2Pesan TypeError tersebut sangat jujur: nilai yang datang bertipe ArrayObject, sedangkan parameter menuntut Countable&Iterator. Memenuhi satu dari dua interface tidak memadai — itulah makna tanda & yang membedakannya dari |. Jika fungsi kita memang ingin menerima kedua class tersebut, union ArrayIterator|ArrayObject adalah bentuk yang tepat; pilihan antara & dan | sepenuhnya mengikuti makna yang ingin dinyatakan.
Larangan Mencampur dengan Union #
Pada PHP 8.1, intersection tidak boleh berdiri di dalam union: Countable&Iterator|ArrayObject adalah kesalahan sintaks. Simpan sebagai campur.php:
<?php
function proses(Countable&Iterator|ArrayObject $data): int
{
return $data->count();
}Output yang Dihasilkan:
Parse error: syntax error, unexpected token "|", expecting variable in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/intersection-types/.verify_tmp.php on line 2Parser menolak campuran tersebut sebelum program berjalan karena tanda & dan | yang berdampingan menimbulkan ambiguitas pembacaan. PHP 8.2 melonggarkan aturan ini dengan syarat penulisan yang eksplisit: bagian intersection dibungkus tanda kurung, membentuk apa yang disebut DNF (Disjunctive Normal Form) types. Bentuk (A&B)|C dibaca sebagai “object yang memenuhi A dan B, ATAU object bertipe C”:
<?php
function proses((Countable&Iterator)|ArrayObject $data): int
{
return $data->count();
}
var_dump(proses(new ArrayIterator(["a", "b", "c"])));
var_dump(proses(new ArrayObject(["x", "y"])));Output yang Dihasilkan:
int(3)
int(2)Kedua pemanggilan kini berhasil: ArrayIterator lolos lewat cabang (Countable&Iterator), sedangkan ArrayObject lolos lewat cabang ArrayObject. Kedua cabang sama-sama menjamin method count() tersedia — dari Countable di cabang pertama dan dari definisi class ArrayObject di cabang kedua — sehingga body fungsi tetap aman dijalankan untuk keduanya. Aturan penulisannya: tanda kurung wajib mengelilingi kelompok intersection, dan tanda kurung tidak boleh dipakai untuk tujuan lain pada deklarasi tipe.
Intersection atau Interface Gabungan #
Ada cara klasik untuk menyatakan “keduanya sekaligus”: mendefinisikan interface baru yang mewarisi kedua interface tersebut, lalu memakainya sebagai tipe:
interface KoleksiTerurut extends BisaDibaca, BisaDiurut
{
}Pendekatan ini sah dan tetap berguna, tetapi keduanya berbeda secara struktural. Tipe BisaDibaca& BisaDiurut diperiksa berdasarkan kemampuan yang benar-benar diimplementasikan sebuah class — class apa pun yang mengimplementasikan kedua interface otomatis lolos, tanpa perlu mengenal KoleksiTerurut. Sebaliknya, interface gabungan mengharuskan class menyatakan implements KoleksiTerurut secara eksplisit, sehingga seluruh kode harus setuju memakai nama yang sama. Untuk kode yang menggabungkan interface dari pustaka berbeda yang tidak bisa kita ubah, intersection types adalah satu-satunya jalan tanpa membuat lapisan perantara.
Ringkasan #
Intersection types dengan sintaks A&B mewajibkan nilai memenuhi seluruh interface yang disebut — ArrayIterator lolos untuk Countable&Iterator sementara ArrayObject tertolak karena hanya memenuhi satu sisi. Campuran dengan union dilarang kecuali ditulis dalam bentuk DNF (A&B)|C sejak PHP 8.2. Dari tipe yang menuntut banyak interface, perjalanan kita berlanjut ke ujung lain spektrum: tipe yang hanya menerima satu nilai spesifik, dibahas pada Chapter Literal Types.