Intersection Types เป็นหนึ่งในฟีเจอร์เด่นที่ถูกเพิ่มเข้ามาตั้งแต่วันที่ PHP 8.1 ซึ่งช่วยให้เราสามารถกำหนด Type ให้กับตัวแปรหรือ Parameter โดยบังคับว่าข้อมูลนั้นจะต้องเป็นสมาชิกของ ทุกๆ Type ที่กำหนดไว้พร้อมกัน (โอเปอเรเตอร์ &)
ถ้าอธิบายให้เห็นภาพง่าย ๆ
- Union Types (
|): เลือกอย่างใดอย่างหนึ่ง (อันนี้หรืออันนั้น) - Intersection Types (
&): ต้องเป็นทั้งหมด (อันนี้และอันนั้น)
ทำไมเราถึงต้องใช้ Intersection Types?
ในโลกของการเขียนโค้ดแบบ Object-Oriented Programming (OOP) เรามักจะแบ่งความสามารถของ Object ออกเป็น Interface ย่อยๆ ตามหลัก Interface Segregation Principle (ซอย Interface ให้เล็กลงเพื่อให้ทำงานเฉพาะทาง)
แต่ปัญหาจะเกิดตอนที่เราอยากสร้างฟังก์ชันที่ต้องการ Object ที่มีความสามารถจาก หลาย ๆ Interface พร้อมกัน ### ตัวอย่างปัญหา (ก่อน PHP 8.1) สมมติว่าเรามี 2 อินเตอร์เฟส คือ Countable (นับจำนวนได้) และ Iterator (วนลูปได้) ถ้าเราอยากสร้างฟังก์ชันที่รับค่าเข้ามาแล้วต้องทั้งนับได้และวนลูปได้ด้วย เราทำได้แค่
- เลือก Type ใด Type หนึ่ง แล้วไปเช็คด้วย
is_a()หรือinstanceofข้างในฟังก์ชันเอาเอง (ซึ่งโค้ดจะไม่สวยและเสี่ยงพังตอน Runtime) - สร้าง Interface ใหม่ขึ้นมาผูกรวมกัน เช่น
interface CountableIterator extends Countable, Iterator {}ซึ่งทำให้เกิดขยะในระบบโดยไม่จำเป็น
วิธีการใช้งานใน PHP 8.1+
ด้วย Intersection Types เราสามารถใช้เครื่องหมายแอมเพอร์แซนด์ (&) เชื่อม Type ที่เราต้องการได้ทันที โดยไม่ต้องสร้าง Interface ใหม่ขึ้นมาให้รกตระกูล
interface Serializable {
public function serialize(): string;
}
interface Loggable {
public function log(string $message): void;
}
// ฟังก์ชันนี้จะรับเฉพาะ Object ที่ implement ทั้ง Serializable และ Loggable เท่านั้น
function processData(Serializable & Loggable $item) {
// สามารถเรียกใช้ Method จากทั้งสอง Interface ได้อย่างปลอดภัย
$item->log("Processing item...");
return $item->serialize();
}
หากเราส่ง Object ที่มีแค่ความสามารถเดียว หรือไม่ครบตามเงื่อนไข PHP จะโยน TypeError ออกมาให้ทันทีตั้งแต่ต้นทาง ช่วยลด Bug ได้มหาศาล
ข้อจำกัดสำคัญที่ควรรู้
แม้ว่าจะทรงพลัง แต่ Intersection Types มีกฎเหล็กที่ต้องจำไว้ดังนี้ครับ
- ใช้ได้กับ Class และ Interface เท่านั้น: คุณไม่สามารถใช้กับ Scalar types เช่น
string & intหรือarray & callableได้ (เพราะมันไม่มีทางที่ตัวแปรเดียวจะเป็นทั้ง string และ int พร้อมกันได้ในทางตรรกะ) - ไม่สามารถผสมกับ Union Types ตรง ๆ ได้ (ใน PHP 8.1): คุณไม่สามารถเขียน
A & B | Cได้
โน้ต: ตั้งแต่ PHP 8.2 เป็นต้นไป คุณสามารถทำได้แล้วผ่านฟีเจอร์ DNF Types (Disjunctive Normal Form) โดยต้องใส่ฟังก์ชันวงเล็บให้ชัดเจน เช่น(A & B) | C
สรุป
Intersection Types ช่วยให้การทำ Type Hinting ใน PHP มีความยืดหยุ่นและปลอดภัยมากขึ้น เหมาะมากสำหรับการเขียนโค้ดสไตล์ Clean Architecture หรือการออกแบบระบบที่เน้น Composition over Inheritance (การประกอบร่างมากกว่าการสืบทอดคลาส)
อ่านเพิ่มเติม