ממשק ב-PHP מצהיר על מתודות שמחלקה מבטיחה לספק, בלי שום קוד: interface Shape { public function area(): float; }. מחלקה שכותבת implements Shape חייבת להגדיר את area(), וכל פונקציה שמשתמשת ברמז הטיפוס Shape תקבל את המחלקה הזו.
describe() לא יודעת ולא אכפת לה איזו מחלקה היא מקבלת. היא מסתמכת רק על ההבטחה שהממשק נותן, ולכן Triangle implements Shape חדשה עובדת איתה בלי לשנות שורה.
תכנות מול ממשק
הסיבה הרגילה לכתוב ממשק היא לאפשר לקוד להיות תלוי ביכולת ולא במחלקה מסוימת. כאן שירות הזמנות מקבל כל Notifier, כך שאותו קוד יכול לשלוח אימייל בפרודקשן ולאסוף הודעות בבדיקה:
מימוש של כמה ממשקים
מחלקה מרחיבה לכל היותר אב אחד, אבל היא יכולה לממש כמה ממשקים שהיא צריכה. רשמו אותם אחרי implements, מופרדים בפסיקים, והגדירו את המתודות של כולם:
JsonSerializable הוא אחד הממשקים המובנים של PHP: מימוש שלו קובע מה json_encode() מפיקה עבור האובייקט.
קבועי ממשק
ממשק יכול להצהיר על קבועים. כל מחלקה שמממשת אותו יורשת אותם, ואפשר לקרוא אותם דרך שם הממשק או שם המחלקה:
לממשקים לא יכולים להיות מאפיינים ב-PHP 8.3, רק מתודות וקבועים. המתודות יכולות להיות סטטיות: public static function fromArray(array $data): static; בממשק מחייבת כל מחלקה להגדיר את המתודה הסטטית הזו. לקבוצה קבועה של ערכים קשורים, enum מתאים לעיתים קרובות יותר מרשימה של קבועי ממשק.
ממשק יכול להרחיב ממשקים אחרים
גם ממשקים משתמשים ב-extends, ובניגוד למחלקות הם יכולים להרחיב כמה בבת אחת. מחלקה שמממשת את ממשק הבת חייבת להגדיר את המתודות של כל השרשרת:
ממשקים מובנים: Countable, IteratorAggregate, Stringable
PHP מגיעה עם ממשקים שמחברים אובייקטים לשפה. Countable גורם ל-count($obj) לעבוד, IteratorAggregate גורם ל-foreach לעבוד, ו-Stringable ממומש אוטומטית על ידי כל מחלקה עם מתודת __toString():
שכחה של מתודה
אם מחלקה מצהירה שהיא מממשת ממשק אבל מחסירה מתודה, PHP נעצרת עם שגיאה קטלנית שמציינת את המתודה החסרה:
הוסיפו מתודת total() והסקריפט ירוץ. מחלקה מופשטת רשאית להשאיר מתודות של ממשק לא מוגדרות, וזה פתח המילוט שהודעת השגיאה מציעה.
ממשק מול מחלקה מופשטת
שניהם מגדירים מתודות שמחלקה חייבת שיהיו לה, ובשניהם אפשר להשתמש כרמז טיפוס. ההבדל הוא מה עוד הם יכולים להכיל וכמה מהם מחלקה יכולה להשתמש:
| ממשק | מחלקה מופשטת | |
|---|---|---|
| גוף של מתודות | אין (רק חתימות) | מתודות מופשטות וגמורות |
| מאפיינים | לא | כן |
| בנאי | אפשר להצהיר על אחד, לעיתים רחוקות שימושי | כן, עם קוד |
| רמת גישה של מתודות | public בלבד | public, protected, private |
| קבועים | כן | כן |
| כמה למחלקה | כל מספר (implements A, B, C) | אחת (extends Base) |
| שימוש טיפוסי | יכולת: Exportable, Countable | קוד משותף למשפחה: Model, Controller |
הם משתלבים היטב: הגדירו את הממשק בשביל רמז הטיפוס, והציעו מחלקת בסיס מופשטת שמממשת אותו עם הקוד המשותף. מחלקות שצריכות בסיס אחר עדיין יכולות לממש את הממשק ישירות.
שאלות נפוצות
מה זה ממשק ב-PHP?
רשימה של חתימות מתודות ציבוריות בלי גוף: interface Logger { public function log(string $msg): void; }. מחלקה שכותבת implements Logger חייבת להגדיר כל מתודה שבו, וכל קוד שמשתמש ברמז הטיפוס Logger מקבל את המחלקה הזו.
האם מחלקה ב-PHP יכולה לממש כמה ממשקים?
כן. רשמו אותם אחרי implements, מופרדים בפסיקים: class File implements Readable, Writable, Countable. המחלקה חייבת להגדיר את המתודות של כולם. כך PHP מכסה את מה שירושה מרובה עושה בשפות אחרות.
מה ההבדל בין ממשק למחלקה מופשטת ב-PHP?
ממשק מצהיר רק על מתודות ציבוריות (ועל קבועים); אין בו מאפיינים ואין בו קוד של מתודות, ומחלקה יכולה לממש הרבה ממשקים. מחלקה מופשטת יכולה להחזיק מאפיינים, בנאים ומתודות גמורות ליד המופשטות שלה, אבל מחלקה יכולה להרחיב רק אחת. השתמשו בממשק ליכולת ("ניתן לייצוא"), ובמחלקה מופשטת לקוד משותף במשפחה של מחלקות.
האם לממשק ב-PHP יכולים להיות מאפיינים?
לא ב-PHP 8.3: ממשק יכול להכיל חתימות מתודות וקבועים (const VERSION = 2;), אבל לא מאפיינים. הצהרות על מאפיינים בממשקים (property hooks) הגיעו רק ב-PHP 8.4.
איך בודקים אם אובייקט מממש ממשק ב-PHP?
השתמשו ב-instanceof: $obj instanceof Countable הוא true כשהמחלקה של האובייקט מממשת את הממשק, ישירות או דרך מחלקת אב. class_implements($obj) מחזירה את השמות של כל הממשקים שהמחלקה מממשת.