PHP 8.1 הביאה את הבשורה שחיכו לה רבות, תמיכה רישמית ב-Enum-ים 🙌. עד גרסה זאת על מנת לממש טיפוס נתונים דומה ב-PHP היה צריך ליצור מחלקה אשר מכילה את הערכים בצורה סטטית.

מה זה Enum?

טיפוס נתונים אשר מגדיר אוסף או קבוצה של ערכים בעלי קשר כלשהו. שמות הערכים לרוב יהיו מזהים אשר מתנהגים כקבועים בשפת התכנות.
לדוגמא: סטטוס יכיל ערכים כמו ״פורסם״, ״טויטה״, ״ממתין לפרסום״ וכד׳.

לרוב נשתמש בו כאשר אנו נרצה לוודא כי למשתנה מסויים או אפילו לפרמטר של פונקצייה יהיה ערך אשר יהיה אחד או יותר מבין הערכים האפשריים שלו. לחילפין, השימוש בו מאפשר לנו לבצע החלטות בלוגיקה של המערכת בהתבסס על הערך של המשתנה.

הבעיה ש-Enum בא לפתור

כאמור, השימוש בו מקל עלינו בכך שהוא מאפשר לנו ליצור שפה משותפת בין חלקים שונים של המערכת ואף לתת ייצוג לערכים מספריים בצורה שמית לצורך הבנה של הקוד. בנוסף, כאשר יש לנו בקוד המערכת שלנו מספר בדיקות אשר מתייחסות לערך כלשהו, נרצה להגדיר אותו כקבוע או כ-Enum.

לדוגמא, נניח שהפונקציה שלנו מקבל ערך כלשהו והערך הינו ערך מספרי, הקוד הבא לא מובן מכיוון שהוא מפר את עיקרון הפשטות של הקוד:

/**
 * Check if post is published.
 *
 * @param int $status Post status.
 *
 * @return bool
 */
function is_published( int $status ) : bool {
	return $status === 3;
}

במידה ומפתח אחר יגש לקוד או אפילו שאנחנו נחזור לקוד אחרי הרבה זמן שלא נגענו בו, אנחנו נבין כנראה ש-3 מייצג ערך של סטטוס פורסם, אך לא נוכל להבין זאת בוודאות מקריאה של הקוד ברפרוף.

דוגמא נוספת, נניח שבקוד שלנו יש עוד פונקציה אשר אחראית על להדפיס את סטטוס הפוסט בהתאם לערך שלו, נוצרת פה תלות ופתחה לבאגים. במידה ונחליט לשנות את הערך של אחד הקבועים של ה-Enum, נצטרך לערוך את זה במספר מקומות בקוד שלנו.

על מנת להתגבר על הבעיות האלו נרצה להשתמש ב-Enum, בכדי לגרום לקוד שלנו להיות יותר קריא וגם בשביל לשמור עליו דינאמי, ככה לא נצטרך להתעסק בעדכונים לאורך הקוד במידה ומשנים ערך כלשהו.

יתרונות ומימוש

אז אחרי שהבנו מה הבעיה ש-Enum בא לפתור, הגיע הזמן ללכלך קצת את הידיים בקוד.
בתור התחלה בואו נראה קוד של יצירת Enum בגרסאות ישנות מ-8.1.

/**
 * Class PostStatus
 */
abstract class PostStatus {
	const DRAFT            = 1;
	const PENDING          = 2;
	const PUBLISHED        = 3;
	const FUTURE           = 4;
	const DELETED          = 10;

	/**
	 * Check whether value is valid Enum value.
	 *
	 * @param int $value Value to check.
	 *
	 * @return bool
	 */
	public static function isSupported( int $value ) : bool {
		return in_array( $value, [
			self::DRAFT,
			self::PENDING,
			self::PUBLISHED,
			self::FUTURE,
			self::DELETED,
		] );
	}
}

כמו שניתן לראות בקוד לעיל ☝️ אנו יוצרים מחלקה חדשה שנקראת PostStatus אשר את הערכים האפשריים אנחו מגדירים כקבועים אשר מאפשרים לנו לגשם בצורה סטטסית מבלי לאתחל את המחלקה.
מלבד הערכים אנו מגדירים מטודה שנקראת isSupported אשר מקבל ערך כלשהו ובודקת האם הוא ערך תקין של ה-Enum.

כדאי לשים לב שהגדרנו שהמחלקה היא אבסטרקטית, זאת על מנת לוודא שלא יוכלו לאתחל את המחלקה.

על מנת להשתמש בו, ניקח את קטע הקוד בחלק הקודם ונמיר אותו לשימוש ב-Enum.

/**
 * Check if post is published.
 *
 * @param int $status Post status.
 *
 * @return bool
 */
function is_published( int $status ) : bool {
	return $status === PostStatus::PUBLISHED;
}

עכשיו שמסתכלים על הקוד, הוא נראה יותר נקי ויותר ברור, איך פה ערכים אשר כתובים Hard Coded וזה מאפשר לנו גמישות גבוהה יותר וגם פישוט של הקוד.

הפתרון הזה נחמד והוא מימוש יחסית פשוט של הערך, הבעיה היא שבכל מקרה שאנו משתמשים ב-Enum, אנו נצטרך להוסיף קריאה לפונקציה isSupported על מנת לוודא שהערך שהועבר הוא בוודאות ערך תקין.

תמיכה רשמית ב-PHP 8.1

אז כאשר אחרי שהבנו מה זה Enum וראינו דוגמאות למימוש של טיפוס נתונים זה, הגיע הזמן ללמוד איך מממשים ב-PHP 8.1.

הגדרת Enum

על מנת להגדירו החל מ-PHP 8.1, נוכל לעשות זאת ע״י שימוש במילת המפתח enum באופן הבא:

enum PostStatus: int {
	case DRAFT = 1;
	case PENDING = 2;
	case PUBLISHED = 3;
	case FUTURE = 4;
	case DELETED = 10;
}

בדוגמא הנ״ל הגדרנו את הערכים כערכים מספריים, אך אנו יכולים להגדיר ערכי מחרוזת במקום באופן הבא:

enum PostStatus: string {
	case DRAFT = 'draft';
	case PENDING = 'pending';
	case PUBLISHED = 'published';
	case FUTURE = 'future';
	case DELETED = 'deleted';
}

לחילופין, אנו יכולים להגדיר יותר מסט ערכים אחד ע״י שימוש בפונקציות אשר קוראות לפונקציה match על מנת להחזיר את הערך הרלוונטי של הקבוע.

enum PostStatus {
	case DRAFT;
	case PENDING;
	case PUBLISHED;
	case FUTURE;
	case DELETED;

	/**
	 * Get status id number.
	 *
	 * @return int
	 */
	public function id() : int {
		return match ( $this ) {
			self::DRAFT => 1,
			self::PENDING => 2,
			self::PUBLISHED => 3,
			self::FUTURE => 4,
			self::DELETED => 10
		};
	}

	/**
	 * Get status slug name.
	 *
	 * @return string
	 */
	public function slug() : string {
		return match ( $this ) {
			self::DRAFT => 'draft',
			self::PENDING => 'pending',
			self::PUBLISHED => 'published',
			self::FUTURE => 'future',
			self::DELETED => 'deleted'
		};
	}
}

על מנת לקבל את הערך הרלוונטי מהסט נוכל לעשות זאת באופן הבא:

// Get status id.
$status = PostStatus::PUBLISHED->id();

// Get status slug name.
$status = PostStatus::PUBLISHED->slug();

שימוש ב-Enum

כעת במידה ונרצה להשתמש ב-Enum שיצרנו על מנת להגדיר שלארגומנט שמועבר לפונקציה יהיה ערך תיקני, נוכל להגדיר את שמו של ה-Enum בתור טיפוס הנתונים של הארגומנט.

/**
 * Check if post is published.
 *
 * @param PostStatus $status Post status.
 *
 * @return bool
 */
function is_published( PostStatus $status ) : bool {
	return $status === PostStatus::PUBLISHED;
}

כאשר אנו משתמשים בהגדרת סוג הארגומט, אנו יכולים להשתמש בכוח של PHP 💪 על מנת לבדוק לנו מראש שהערך שהועבר לפונקציה הוא אכן ערך תקני, כך אנו לא צריכים להוסיף בדיקה שבודקת האם הערך הוא קיים בו.

מגבלות השימוש

אחד המגבלות שיש לשימוש ב-Enum ב-PHP 8.1 זה שאנו לא יכולים להשתמש בקבועים שלו כמפתחות במערך.

$statuses = [
	PostStatus::DRAFT => 'draft',
	...
];

במידה ונכתוב את הקוד הבא ונריץ אותו, תזרק שגיאת תחביר:

צילום מסך של הטרמינל המציג שגיאת תחביר עם Enum

סיכום

PHP בשנים האחרונות מתפתחת מאוד ומכניסה המון פיצ׳רים וכלים אשר נותנים למפתחים בשפה כוח שלא היה עד כה. Enum הוא אחד מאותם פיצ׳רים אשר חיכו לו המון זמן ואני חייב להגיד שעשו שם עבודה מעולה.
המאמר דגם על קצה המזלג את אופן ההגדרה והשימוש ב-PHP 8.1, אני ממליץ לכם לקרוא עוד אודות המימוש בדוקומנטציה הרישמית של PHP.

    כתיבת תגובה

    1. אלון

      מעניין, תודה רבה!

      הגב
      1. דור צוברי

        היי אלון 👋,
        שמח לשמוע! מזמין אותך להירשם לעדכונים במייל או בטלגרם 🙂

    2. איתי בנר

      הי דור, תודה על העדכון. מה שלא ברור לי הוא – בדוגמה שלך – איך אני סוגר את המעגל הזה ומבצע את הבדיקה על פוסט כלשהו. הרי צריך להכניס Post ID איפשהו. אני חושב שאני מבין את מטרת הדוגמה שנתת, אבל מתקשה לדמיין את היישום שלה בפועל עד הסוף.

      הגב
      1. דור צוברי

        היי איתי 👋,
        בוורדפרס זה פחות שמיש, אך כאשר מפתחים מערכות אשר משתמשות במפתחות API כלשהם או טוקנים, לא מומלץ לשמור אותם במסד נתונים, לכן נגדיר אותם כמשתנה סביבה.

    אפשר להציע לך עוגיות? יש גם קפה! השימוש בקוקיז עוזר לשפר את הביקור שלך באתר. המשך גלישה אומר שהסכמת למדיניות הפרטיות שלי, וגם לקפה.

    שתפו