trait ב-PHP הוא בלוק של מתודות ומאפיינים לשימוש חוזר. מצהירים עליו עם trait Name { ... } ומושכים אותו לכל מחלקה עם use Name; בתוך גוף המחלקה. PHP מעתיקה את החברים של ה-trait לתוך המחלקה, ולכן הם עובדים בדיוק כאילו כתבתם אותם שם.
ל-User ול-Robot אין אב משותף, ובכל זאת לשתיהן יש עכשיו hello(). traits קיימים בשביל המקרה הזה: מחלקות ב-PHP יכולות להרחיב רק מחלקה אחת, ו-trait משתף קוד הצידה, בין מחלקות שאינן קשורות.
traits עם מאפיינים
trait יכול להביא מאפיינים יחד עם המתודות שלו. המתודות משתמשות ב-$this כמו כל מתודה של המחלקה, כי ברגע שה-trait מועתק פנימה, הן מתודות של המחלקה:
מחלקה לא יכולה להצהיר על מאפיין עם אותו שם כמו מאפיין של trait אלא אם שתי ההצהרות זהות (אותה רמת גישה, טיפוס וערך ברירת מחדל); אחרת זו שגיאה קטלנית.
שימוש בכמה traits
רשמו כמה traits בפקודת use אחת, מופרדים בפסיקים, או כתבו כמה שורות use. המחלקה מקבלת את החברים של כולם:
פתרון התנגשויות עם insteadof ו-as
כששני traits מגדירים מתודה עם אותו שם, PHP מסרבת לנחש ונעצרת עם שגיאה קטלנית. בתוך בלוק use, insteadof בוחרת את המנצח, ו-as שומרת את המתודה המפסידה תחת שם אחר:
עם use EnglishGreeting, FrenchGreeting; פשוט ובלי בלוק, הסקריפט נעצר לפני שמשהו מודפס:
PHP Fatal error: Trait method FrenchGreeting::greet has not been applied as Host::greet, because of collision with EnglishGreeting::greet in /home/index.php on line 12
as יכולה גם לשנות רמת גישה. use Loggable { log as protected; } הופכת את log() של ה-trait למוגנת רק במחלקה הזו, ו-use Loggable { log as private writeLog; } מוסיפה כינוי פרטי.
חברים מופשטים וסטטיים ב-trait
trait יכול לדרוש משהו מהמחלקה שמשתמשת בו על ידי הצהרה על מתודה מופשטת. יכולים להיות לו גם מתודות סטטיות ומאפיינים סטטיים. כל מחלקה שמשתמשת ב-trait מקבלת עותק משלה של מאפיין סטטי:
Car ו-Bike סופרות בנפרד, כי המאפיין הסטטי מועתק לכל מחלקה. זה שונה ממאפיין סטטי שמוצהר במחלקת אב משותפת, שכל הבנות חולקות; העמוד static מראה את המקרה הזה.
איזו מתודה מנצחת: מחלקה, trait או אב
אם אותו שם מתודה קיים במחלקה עצמה, ב-trait שהיא משתמשת בו ובמחלקת האב שלה, PHP בוחרת בסדר הזה: המתודה של המחלקה עצמה, אחר כך של ה-trait, אחר כך של האב. בתוך ה-trait, parent:: עדיין מגיע לגרסה של האב:
trait אינו טיפוס
instanceof אף פעם לא מתאים ל-trait, ופרמטר עם טיפוס של שם trait דוחה כל אובייקט עם TypeError. כדי לשאול "האם למחלקה הזו יש את ההתנהגות", חברו את ה-trait לממשק, או בדקו את המחלקה עם class_uses():
הדפוס שלמעלה הוא הרגיל: הממשק Exportable הוא מה שפונקציות משתמשות בו כרמז טיפוס ומה ש-instanceof בודק, וה-trait ExportsProperties הוא מימוש מוכן שמחלקות יכולות להשתמש בו או להחליף אותו. trait לבדו הוא קוד משותף בלי חוזה; כשאתם שמים לב שאתם בודקים class_uses() בלוגיקה העסקית, ה-trait רצה ממשק לצידו. לקוד שמשותף למשפחה של מחלקות קשורות, מחלקה מופשטת עשויה להיות הבחירה הפשוטה יותר.
שאלות נפוצות
מה זה trait ב-PHP?
trait הוא קבוצה עם שם של מתודות (ומאפיינים) שמוצהרת עם trait Name { } ומחלקה מושכת אותה פנימה עם use Name;. PHP מעתיקה את החברים של ה-trait לתוך המחלקה, ולכן הם מתנהגים כאילו כתבתם אותם שם. זו דרך לשתף קוד בין מחלקות שאין להן אב משותף.
האם מחלקה ב-PHP יכולה להשתמש בכמה traits?
כן: use Timestamps, SoftDeletes; או כמה שורות use. אם שני traits מגדירים מתודה עם אותו שם, חייבים לבחור אחת עם insteadof, למשל use A, B { A::hello insteadof B; }, ואפשר לשמור את השנייה תחת שם חדש עם B::hello as helloB;.
מה ההבדל בין trait לממשק ב-PHP?
ממשק הוא טיפוס בלי קוד: הוא אומר אילו מתודות חייבות להיות למחלקה, ו-instanceof עובד איתו. trait הוא קוד בלי טיפוס: הוא מדביק גופי מתודות לתוך מחלקה, אבל $obj instanceof SomeTrait הוא תמיד false. לעיתים קרובות משלבים אותם: ממשק בשביל הטיפוס, trait עם מימוש ברירת מחדל.
האם ל-trait יכול להיות בנאי או מתודות מופשטות?
כן לשניהם. trait יכול להצהיר abstract public function name(): string; כדי לדרוש מהמחלקה שמשתמשת בו לספק את המתודה הזו, והוא יכול להגדיר __construct, אם כי בנאי ב-trait מתנגש בקלות עם הבנאי של המחלקה עצמה ולעיתים רחוקות הוא רעיון טוב.
איזו מתודה מנצחת אם מחלקה ו-trait מגדירים את אותה מתודה?
המתודה של המחלקה עצמה מנצחת את זו של ה-trait, והמתודה של ה-trait מנצחת מתודה שעברה בירושה ממחלקת האב. אז הסדר הוא: המחלקה, אחר כך ה-trait, אחר כך האב.