วัน: 5 ธันวาคม 2021

  • PHP: First-class Callable Syntax วิธีอ้างอิงฟังก์ชันแบบโมเดิร์น

    นับตั้งแต่ PHP 8.1 เป็นต้นมา มีการฟีเจอร์หนึ่งที่ถูกเพิ่มเข้ามาและช่วยเปลี่ยนวิถีการเขียนโค้ดของเราให้สะอาดขึ้นอย่างมาก นั่นคือ First-class Callable Syntax ครับ หากคุณเคยเบื่อกับการที่ต้องส่งชื่อฟังก์ชันเป็น String หรือส่ง Array แปลก ๆ เวลาทำ Callback ฟังก์ชันนี้คือคำตอบครับ


    ปัญหาของการอ้างอิงฟังก์ชันแบบเดิม (ก่อน PHP 8.1)

    ก่อนหน้านี้เวลาที่เราต้องการนำฟังก์ชันหรือเมธอดไปเป็นอาร์กิวเมนต์ (Callback) ในฟังก์ชันอื่น เช่น array_map หรือ usort เรามักจะใช้วิธีส่งเป็น String หรือ Array ดังนี้ครับ

    // 1. อ้างอิงฟังก์ชันทั่วไปด้วย String
    $lengths = array_map('strlen', ['apple', 'banana']);
    
    // 2. อ้างอิง Method ของ Object ด้วย Array [$object, 'method']
    $processor = new DataProcessor();
    $result = array_map([$processor, 'cleanData'], $dirtyData);
    
    // 3. อ้างอิง Static Method ด้วย String "Class::method" หรือ Array
    $slugs = array_map('StringHelper::slugify', $titles);

    ทำไมวิธีเดิมถึงมีปัญหา?

    • ไม่มี Type Safety: IDE ไม่รู้ว่า String หรือ Array นั้นคือฟังก์ชันจริง ๆ หรือเปล่า จนกว่าโค้ดจะรัน (Runtime)
    • หา Bug ยาก: ถ้าคุณพิมพ์ชื่อฟังก์ชันผิดใน String (เช่น 'str_len' แทน 'strlen') IDE จะไม่แจ้งเตือน (No Static Analysis support)
    • Refactor ลำบาก: เวลาจะเปลี่ยนชื่อ Method ด้วยเครื่องมือใน IDE ชื่อใน String มักจะไม่ถูกเปลี่ยนตามไปด้วย

    พระเอกมาแล้ว: First-class Callable Syntax คืออะไร?

    ใน PHP 8.1 ได้นำรูปแบบการเขียนแบบใหม่มาใช้ โดยการใช้เครื่องหมาย ... (Ellipsis) ต่อท้ายชื่อฟังก์ชันหรือเมธอดที่เราต้องการอ้างอิง ระบบจะทำการสร้าง Closure (หรืออ็อบเจกต์ที่เรียกใช้งานได้) ขึ้นมาให้เราทันที โดยที่เราไม่ต้องครอบด้วย String อีกต่อไป

    รูปแบบโครงสร้าง (Syntax)

    ฟังก์ชันที่ต้องการ(…)


    เปรียบเทียบการใช้งาน: อดีต VS ปัจจุบัน

    ลองมาดูกันครับว่าเมื่อเปลี่ยนมาใช้ First-class Callable Syntax แล้ว โดคจะดูดีขึ้นขนาดไหน


    ฟังก์ชันทั่วไป (Standard Functions)

    • แบบเดิม: 'strlen'
    • แบบใหม่: strlen(...)
    $words = ['hello', 'php', 'world'];
    // แบบใหม่: ไม่ต้องใส่เครื่องหมายคำพูดอีกต่อไป
    $lengths = array_map(strlen(...), $words);

    เมธอดของอ็อบเจกต์ (Instance Methods)

    • แบบเดิม: [$this, 'formatDate']
    • แบบใหม่: $this->formatDate(...)
    class UserRenderer {
        public function format(array $users): array {
            // อ้างอิงเมธอดภายใน Class ตัวเองได้หล่อๆ
            return array_map($this->renderRow(...), $users);
        }
    
        private function renderRow(User $user): string {
            return "<li>{$user->name}</li>";
        }
    }

    สแตติกเมธอด (Static Methods)

    • แบบเดิม: ['Helper', 'generateUuid'] หรือ 'Helper::generateUuid'
    • แบบใหม่: Helper::generateUuid(...)
    $ids = [1, 2, 3];
    // IDE รู้ทันทีว่าเรียกใช้ Static Method ตัวไหน
    $uuids = array_map(UUIDGenerator::fromId(...), $ids);

    ข้อดีที่คุณจะได้รับทันทีที่ย้ายมาใช้

    1. IDE Friendly & Autocomplete: สิ้นสุดยุคเดาชื่อฟังก์ชันใน String เพราะตอนนี้ IDE (เช่น PHPStorm, VS Code) จะมีระเบิดความสามารถ Autocomplete ขึ้นมาให้ และช่วยเช็กให้ทันทีว่าฟังก์ชันนั้นมีอยู่จริงไหม
    2. ปลอดภัยตอน Refactor: ถ้าคุณเปลี่ยนชื่อเมธอด ตัว IDE จะไล่เปลี่ยนโค้ดที่ใช้ ... ให้โดยอัตโนมัติ โอกาสเกิด Bug ลดลงมหาศาล
    3. ประสิทธิภาพที่จับต้องได้: การใช้ ... จะสร้าง Closure ขึ้นมาตั้งแต่ตอนที่คอมไพล์ ซึ่งเร็วกว่าและกินทรัพยากรน้อยกว่าการให้ PHP ไปแกะ String/Array ในตอน Runtime

    ข้อควรรู้เพิ่มเติม

    [!NOTE] ฟีเจอร์นี้ไม่สามารถใช้ร่วมกับ ตรรกะการเรียกแบบ Dynamic ที่ระบุชื่อฟังก์ชันในตัวแปรตรง ๆ ได้ เช่น $func(...) (ถ้า $func เป็น string) แต่คุณสามารถเลี่ยงไปใช้ Closure::fromCallable($func) แทนได้หากจำเป็นจริง ๆ


    สรุป

    First-class Callable Syntax (...) เป็นการยกระดับภาษา PHP ให้มีความเป็น Modern Language มากขึ้น มันช่วยปิดช่องโหว่เรื่องความผิดพลาดจากการพิมพ์ String ผิด และทำให้เครื่องมือพัฒนาทำงานได้อย่างเต็มประสิทธิภาพ

    ถ้าโปรเจกต์ของคุณรันบน PHP 8.1 ขึ้นไป แล้วล่ะก็ แนะนำให้เริ่มเปลี่ยนมาใช้รูปแบบนี้แทน String หรือ Array Callback ได้เลยครับ โค้ดสะอาดขึ้นแน่นอน!


    อ่านเพิ่มเติม