PHP 8.1 เพิ่มการรองรับ Enum

19 กุมภาพันธ์ 2564
PHP 8.1 เพิ่มการรองรับ Enum

PHP 8.1 กำลังอยู่ระหว่างการพัฒนาและคาดว่าจะเริ่มปล่อยให้ใช้ได้ช่วงปลายเดือน พฤศจิกายน 2021 ฟีเจอร์หนึ่งที่เพิ่มขึ้นมาคือการอองรับ Enum

เราจะมาเริ่มดูกันที่วิธีประกาศ enum ใน PHP8.1 กันเลย

enum Status
{
    case DRAFT;
    case PUBLISHED;
    case ARCHIVED;
}

ข้อดีของการใช้ enum คือ ไว้ใช้ประกาศค่าคงที่หลาย ๆ ตัวที่เป็นกลุ่มเดียวกันโดยที่ค่าพวกนี้สามารถในไปใช้ type-hint ได้

class Post
{
    public function __construct(
        public Status $status, 
    ) {}
}

จากตัวอย่างนี้ เราได้สร้าง enum แล้วส่งค่าให้ Post ได้แบบนี้

$post = new Post(Status::DRAFT);

อันนี้เป็นเพียงวิธีการใช้งานแบบพื้นฐานไม่มีอะไรซับซ้อนไม่ต่างจากการประกาศค่าคงที่ในคลาสตามปกติ แต่ enum ยังมีความสามารถมากกว่านี้ ลองมาดูกันเลย

Enum สามารถมี method ได้

Enum สามารถมีเมธอดได้เหมือนกับคลาส อันนี้เป็นฟีเจอร์ที่มีความสามารถมาก เช่น หลาย ๆ ครั้งเราต้องการเอา status ไปแสดงเป็น Label แล้วต้องมีการ style สีของ label หรือ ทำ title ของ label ตาม status ปัจจุบัน เราสามารถนำไปใช้ร่วมกับ match operator ซึ่งเป็น syntax ใหม่ที่ถูกเพิ่มเข้ามาตั้งแต่ PHP 8 ด้วย ตามตัวอย่างนี้

enum Status
{
    case DRAFT;
    case PUBLISHED;
    case ARCHIVED;
    case TRASHED;
    
    public function color(): string
    {
        return match($this) 
        {
            Status::DRAFT => 'grey',   
            Status::PUBLISHED => 'green',   
            Status::ARCHIVED,   
            Status::ARCHIVED => 'red',   
        };
    }

    public function title(): string
    {
        return match($this) 
        {
            Status::DRAFT => 'ร่าง',   
            Status::PUBLISHED => 'เผยแพร่',   
            Status::ARCHIVED => 'ประวัติ',   
            Status::TRASHED => 'ถังขยะ',   
        };
    }
}

เมธอดใน Enum สามารถเรียกใช้งานได้แบบนี้

$status = Status::ARCHIVED;

$status->color(); // 'red'
$status->color(); // 'ประวัติ'

ใน enum ก็สามารถใช้ static method ได้เหมือนกัน แล้วก็สังเกตว่าเราสามารถใช้ self ใน enum ได้เหมือนกับในคลาส

enum Status
{
    // …
    
    public function color(): string
    {
        return match($this) 
        {
            self::DRAFT => 'grey',   
            self::PUBLISHED => 'green',   
            self::ARCHIVED => 'red',   
        };
    }
}

Enum interface

Enum สามารถ implement interface ได้ เหมือนคลาสปกติทั่วไป

interface HasColor
{
    public function color(): string;
}
enum Status implements HasColor
{
    case DRAFT;
    case PUBLISHED;
    case ARCHIVED;
    
    public function color(): string { /* … */ }
}

Enum แบบมี value — หรือ "Backed enums"

ค่าของ enum ภายใน PHP จริง ๆ แล้วเก็บเป็น Object แต่เราสามารถกำหนดค่าให้ enum แต่ละตัวได้ ทำแบบนี้จะมีประโยชน์ เช่น กรณีที่เราต้องการเก็บค่าลง database

enum Status: string    // สังเกคประกาศ type ของ enum
{
    case DRAFT = 'draft';
    case PUBLISHED = 'published';
    case ARCHIVED = 'archived';

    // แต่ละค่า มี value เป็น string
}

สังเกต การประกาศ type หลังชื่อ enum (enum Status: string) ตามตัวอย่างด้านบน เป็นการบอกว่าค่า enum จะมี type ตามที่ระบุ เราสามารถระบุ type เป็น int ได้เช่นกัน

เฉพาะ type int และ string เท่านั้นที่สามารถระเป็น enum value ได้

enum Status: int
{
    case DRAFT = 1;
    case PUBLISHED = 2;
    case ARCHIVED = 3;
}

ตามเทคนิคแล้ว เราเรียก enum ที่มีการระบุ type แบบนี้ว่า Backed Enum เพราะเรามีการระบุค่าให้กับแต่ละ case ใน enum เราไม่สามารถปนกันระว่าง case ที่มี value มารับ กับ case ที่ไม่มี value มารับได้ ถ้า enum ที่มีแต่ case โดยไม่มี value มารองรับเหมือนกันตัวอย่างแรกสุดของเรา เราจะเรียก enum แบบนี้ว่า Pure Enum

Backed Enums ที่มี interfaces

ถ้าเราต้องการใช้ทั้ง backed enum และมีการ implement interface ด้วย เราต้องประกาศ enum type ต่อจากชื่อ enum และก่อนคีย์เวิร์ด implements

enum Status: string implements HasColor
{
    case DRAFT = 'draft';
    case PUBLISHED = 'published';
    case ARCHIVED = 'archived';
    
    // …
}

Serializing backed enums

ถ้าเรามีการกำหนดค่าให้กับ enum เราอาจจะต้องการ serialize หรือ deserialize enum ด้วยเหมือนกัน เช่น แปลงเป็น array หรือ json เพื่อ save ลง database หรือใช้ใน frontend โดยเราสามารถเรียกได้ผ่าน value ซึ่งเป็น readonly public property ของ enum

$value = Status::PUBLISHED->value; // 2

การคืนค่า enum จากค่า สามารถทำได้ผ่านเมธอด Enum::form

$status = Status::from(2); // Status::PUBLISHED

นอกจากนี้ยังมีเมธอด tryFrom() ที่จะ return null ถ้าหาค่าที่ระบุไม่พบ แต่ถ้าเราใช้ from จะเป็น exception

$status = Status::from('unknown'); // ValueError
$status = Status::tryFrom('unknown'); // null

method ที่มีชื่อเป็น tryX จะพบได้มากในภาษา C# และ Rust ใช้เพื่อบ่งชี้ว่าผลลัพธ์ที่ return มาอาจเป็น null หรือไม่มีค่าได้ เราอาจจะได้เห็น PHP เริ่มเอาสไตล์การตั้งชื่อ method แบบนี้มาใช้มากขึ้นได้

เพิ่มเติมว่าเรายังสามารถใช้ฟังก์ชัน serialize และ unserialize กับ enum ได้ แล้วใช้ json_encode ร่วมกับ backed enum ได้ โดยค่าจะเป็น enum value เราสามารถ override พฤติกรรมนี้ได้โดย implment JsonSerializable

แสดงค่าทั้งหมดใน enum

เราสามารถใช้ static method Enum::cases() เพื่อแสดง array ของค่าทั้งหมดที่มีใน enum ได้

Status::cases();

/* [
    Status::DRAFT, 
    Status::PUBLISHED, 
    Status::ARCHIVED
] */

โดย array ที่ได้จะเก็บ object enum แต่ละตัว

array_map(
    fn(Status $status) => $status->color(), 
    Status::cases()
);

ถ้าเราใช้ Enum::cases() กับ backed enums, เราจะได้ enum value ใน array key

Status::cases();

/* [
    'draft' => Status::DRAFT, 
    'published' => Status::PUBLISHED, 
    'archived' => Status::ARCHIVED,
] */

Enum เป็น Object

อย่างที่ได้กล่าวไปในตอนต้นแล้วว่า enum เก็บค่าเป็น Object ในความจริงแล้วต้องบอกว่าเป็น Singleton Object นั่นแปลว่าเราสามารถนำ enum มาเปรียบเทียบกันได้

$statusA = Status::PENDING;
$statusB = Status::PENDING;
$statusC = Status::ARCHIVED;

$statusA === $statusB; // true
$statusA === $statusC; // false
$statusC instanceof Status; // true

Enum สามารถใช้ร่วมกับ Trait ได้

Enum สามารถใช้ร่วมกับ trait ได้โดยจะมีผลเหมือนกับใช้ร่วมกับคลาส ข้อจำกัดอย่างหนึ่งคือ trait ที่นำมาใช้ใน enum ต้องไม่มี property จะมีได้เฉพาะ method หรือ static method เท่านั้น ถ้า Enum ที่เอา trait ที่มี property มาใช้จะทำให้เกิด fatal error

trait Label {
    public function label() {
        return "<span>{$this->value}</span>";
    }
}

enum Status: string implements HasColor
{

    use Label;

    case DRAFT = 'draft';
    case PUBLISHED = 'published';
    case ARCHIVED = 'archived';
    
    // …
}

สรุป

ในบทความนี้เราพามาทำความรู้จักกับ Enum ซึ่งเป็นความสามารถใหม่ที่มาใน PHP 8.1 เราได้อธิบายการใช้ Enum ไว้ดังนี้

  • การประกาศ Enum (Pure Enum)
  • การใช้ Enum แบบระบุค่า (Backed Enum)
  • Backed Enum สามารถมีค่าได้เป็น int หรือ string เท่านั้น
  • รู้จักกับ method ที่มีใน enum
  • Enum::cases() แสดงค่าทั้งหมดใน enum
  • Enum::from() return Object enum จากค่าที่ระบุ
  • Enum::tryFrom() return Object enum จากค่าที่ระบุ return null ถ้าหาค่าไม่พบ

อ้างอิง

  • ถ้าผู้ที่สนใจอยากรู้จักกับ Enum ใน PHP 8.1 ลองดูที่มาจาก PHP RFC: Enumeration ได้เพิ่มเติม
  • stitcher.io

Phattarachai Chaimongkol
เกี่ยวกับ phattarachai.dev

สวัสดีครับ เป็น Full Stack Web Developer สามารถดูแลงานทั้งระบบได้ ทำงานสายนี้มากว่า 10 ปี ผ่านงานทางภาครัฐ เป็นที่ปรึกษาบริษัทเอกชน มีงาน Web App ทั้งเล็กและใหญ่ ระบบ Inventory, งาน ERP พร้อมใช้ประสบการณ์ที่มีแก้ไขปัญหาธุรกิจให้ลูกค้าครับ

เรื่องล่าสุด

งานพัฒนา Platform Video E-learning
19 กุมภาพันธ์ 2564
งานพัฒนา Platform Video E-learning
รับงาน Web Scrapping อ่านข้อมูลเว็บไซต์ส่งแจ้งเตือน Line Notification
19 กุมภาพันธ์ 2564
รับงาน Web Scrapping อ่านข้อมูลเว็บไซต์ส่งแจ้งเตือน Line Notification
รับงาน ดึงข้อมูลโพสต์ Facebook Group / รูปภาพ / เนื้อหา ลงไฟล์ ส่งเข้า Google Drive
19 กุมภาพันธ์ 2564
รับงาน ดึงข้อมูลโพสต์ Facebook Group / รูปภาพ / เนื้อหา ลงไฟล์ ส่งเข้า Google Drive