عبدالفتاح الصلوي

البيانات من نوع Enum في php 8.1

كتبها : عبدالفتاح الصلوي / في رصيف : PHP

البيانات من نوع Enum في php 8.1

ماذا تعرف عن ال Enums ؟

بشكل عام في عالم البرمجة، ال enum هو عبارة عن نوع بيانات يتكون من قيم مسماة (إسم وقيمة) تدعى العناصر أو الحالات، وفي بعض لغات البرمجة، يمكن أن يحتوي على دوال.

غالبًا ما تستخدم Enums لتمثيل مجموعة ثابتة من القيم ، مثل أيام الأسبوع أو أشهر السنة أو حالة الطلبات في المتاجر الإلكترونية. يمكن استخدامها أيضًا لتمثيل مفاهيم أكثر تعقيدًا ، مثل الأنواع المختلفة من الأخطاء التي يمكن أن تحدث في البرنامج.

تقدم Enums عددًا من المزايا مقارنة باستخدام النصوص أو الأعداد الصحيحة لتمثيل هذه الأنواع من القيم. فيما يلي بعض الميزات: - تساعد ال Enums في تحسين قابلية قراءة الكود. فبدلاً من استخدام قيم رقمية مثل 0,1 يمكن كتابة Published و Draft مع اسناد القيم الى حالات لتخزن نفس القيمة مع قابلية الفهم والغرض من الحالة. - يمكنها المساعدة في منع الأخطاء من خلال ضمان استخدام القيم الصالحة فقط. للمتغيرات. - يمكن استخدامها لتنفيذ ميزات أكثر تعقيدًا ، مثل عبارات التبديل ومطابقة الأنماط.

البيانات من نوع Enum في php 

من أبرز الميزات التي تم اضافتها للغة في الإصدار 8.1 الذي تم إطلاقه في 25 نوفمبر 2021. فيما سبق كان المطورين يلجؤن إلى إستخدام الأصناف مع الثوابت للإستعاضة عن نوع البيانات المتعددة (Enums). الصيغة العامة لكتابة الenum تبدو مشابهة لصيغة كتابة الصنف (class) مع استبدال الكلمة المحجوزة class ب enum. وكل حالة يتم استخدام الكلمة المحجوزة case لتعريفها . مثال:

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

الان للوصول إلى أي حالة يكفي أن نكتب اسم التعداد ومن ثم الحالة مفصولة ب :: وكأننا نتعامل مع متغير ثابت مثال Status::DRAFT لكن ماذا سيعيد لنا هذا الكود؟ كل حالة عبارة عن object من نوع enum

            var_dump(Status::DRAFT);
            //return enum(Status::DRAFT)
            var_dump(gettype(Status::DRAFT))
            // return string 'object' (length=6)
        

كما أشرنا فإن قيم ال enums عبارة عن objects بالأصح هي عبارة عن singleton objects مما يعني انه يمكن أن نقارن بينها كما يلي

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

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

Enum methods الدوال
يمكن أن تحتوي ال enum على دوال ويتم تعريفها بذات الطريقة المستخدمة في الاصناف (الكلاسات) حيث من الممكن أن تكون دوال عادية أو ثابتة . في الطبع هذا يجعلها اكثر عملية وخاصة مع تعبير match الشرطي. مثال

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

                public function color(): string
                {
                    return match($this)
                    {
                        Status::DRAFT => 'grey',
                        Status::PUBLISHED => 'green',
                        Status::ARCHIVED => 'red',
                    };
                }
            }
            // هكذا يمكن الوصول الى الخصائص والدوال
            $status = Status::ARCHIVED;
            $status->color(); // 'red'
        

كما يمكن استخدام الكلمة المحجوزة self

            enum Status
            {
                // …

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

حسنا، ماذا لو أردنا أن نعيد قيم نصية أو رقمية بالطبع يمكن ذلك ، وهذا يقودنا إلى ما يسمى "Backed enums"

قيم التعداد Enum values

تدعى "Backed enums" حيث يتم تمثيل قيم التعداد بواسطة الكائنات داخليًا ، ولكن يمكنك تعيين قيمة لها إذا كنت تريد ذلك ؛ هذا مفيد على سبيل المثال. تخزين تلك القيم في قاعدة بيانات.

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

لاحظ بأننا قمنا بالتصريح عن نوع البيانات عند تعريف الenum. حيث يعني إلى أن جميع القيم هنا عبارة عن string نص . يمكن أيضاً ان تكون عبارة عن رقم int. في الحقيقة هذه هي الأنواع المسموح باستخدامها كقيم في الenum فإما أن تكون int أو string . ومع التنبيه بأن جميع القيم يجب أن تكون من نوع واحد (إما نصوص أو أرقام) ولا يسمح بأن تكون مختلطة (نصوص وأرقام) أو أن لا يتم اسناد بعضها .
كما أشرنا فهذا النوع من ال enums يسمى "backed" لأنها ترجع قيم بسيطة ، أما النوع الأساسي الذي لا يعيد قيم نصية أو رقمية فيسمى "pure enums".
مثال آخر باستخدام القيم الرقمية .

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

دعنا نحاول الوصول الآن إلى إحدى القيم Status::DRAFT برايك ماذا ستعيد ؟

            var-dump(Status::DRAFT) ;// enum(Status::DRAFT) : int 1
        

ستلاحظ انه لا فرق ملحوظ ما زالت تعيد object فماذا إذا أردنا الوصول إلى القيم الرقمية أو النصية المسندة إليها ؟
هذا يقودنا للحديث عن (name , value ) وهما خاصيتان للقراءة فقط

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

Enum::from وEnum::tryFrom

أما اذا كان لدينا قيمة ونريد أن نعرف أي حالة تمثل فلدينا دوال Enum::from وEnum::tryFrom. مثال استخدام from

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

ماذا عن tryFrom ?

تشبه سابقتها مع وجود فرق جوهري ، حيث أنه في from لو أن القيمة الممرة ليست موجودة ضمن القيمة في ال enum سيتم رمي استثناء من نوع ValueError عكس tryFrom التي ستعيد null ;

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

إعادة كافة القيم

يمكن استخدام الدالة cases التي ستقوم بإعادة كافة القيم في ال enum على شكل مصفوفة

           var_dump(Status::cases());
           /*
           
            */
        

يمكن أيضا استخدام دوال مثل serialize و unserialize وايضا json_encode مع Backed enums

            $serialize = serialize(Status::cases()); // convert to string
            var_dump($serialize); // 'a:3:{i:0;E:12:"Status:DRAFT";i:1;E:16:"Status:PUBLISHED";i:2;E:15:"Status:ARCHIVED";}'
            var_dump(unserialize($serialize)); // convert back to enum
            /*
            array (size=3)
            0 => enum(Status::DRAFT) : int 1
            1 => enum(Status::PUBLISHED) : int 2
            2 => enum(Status::ARCHIVED) : int 3
            */
            var_dump(json_encode(Status::cases())); // '[1,2,3]'
        

تكمن فائدة التعدادات في أنها تمثل مجموعة من القيم الثابتة ، ولكن الأهم من ذلك أنه يمكن كتابة هذه القيم ، مثل:

            class Post
                {
                    public function __construct(
                        public Status $status,
                    ) {}
                }
                //   تستخدم كما يلي
                $post = new Post(Status::DRAFT);
        

وهذا يعني انه لن يتم قبول أي قيمة غير معرفة في ال Status enum

ماذا عن ال Traits وال interface ؟

يمكن استخدام ال Traits مع ال enum مثلما تستخدم مع ال class بشرط أن لا يحتوي ال trait على خصائص

            interface HasColor
            {
                public function getColor(): string;
            }

            trait Color
            {
                //public $name = 'red'; // not allowed
                //public $test = 'test'; // not allowed
                public function getColor(): string
                {
                    return match ($this) {
                        Status::DRAFT => 'grey',
                        Status::PUBLISHED => 'green',
                        Status::ARCHIVED => 'red',
                    };
                }
            }
            enum Status: int implements HasColor
            {
                use Color;
                case DRAFT = 1;
                case PUBLISHED = 2;
                case ARCHIVED = 3;
            }
        

المصادر:
- التوثيق الرسمي للغة
- توثيق المسودة الأولية للميزة
- مقال Brent في مدونة stitcher

علامات ذات صلة :