תוכן עניינים
החל מגרסה 5, PHP מציעה תמיכה מלאה-יותר (אך לא מלאה) של עקרונות תכנות מונחה עצמים,
כשאחת התכונות החדשות שנוספו היא Magic Methods – גם אם לא שמעתם עד היום על המונח הזה, אני מבטיח לכם שיצא לכם ליישם פונקציה שכזו עשרות פעמים!
—
לפני שאסביר מה זה Magic Methods, נחליט (אני ואתם) שמעתה ואילך, המונח Magic Methods ייקרא בעברית 'מתודות קסם', וייעשה בו שימוש לסירוגין בפוסט.
אני לא מת על השם הזה, אז אם למישהו מהקוראים יש הצעה לשם יותר 'סקסי', אשמח לשמוע בתגובות – הזוכה המאושר יזכה להכרת תודה בפוסט 🙂
—
מתודות קסם? Magic Methods? מה אתה רוצה מחיינו?!
אני מתנצל מעומקי ליבי אם אני מאכזב מי מקוראיי, אבל אין קסם ב – PHP (לפחות עוד לא הוכח..), והכוונה במתודות קסם היא לפונקציות מחלקה שמורות (נכון לכתיבת הפוסט יש כ – 15 כאלה), שנקראות אוטומטית במצבים מסויימים.
אפשר להקביל אותן ל – Event Listeners ב – JavaScript, אם כי הקונספט לא זהה.
כל Magic Method מתחילה עם מקדם __
(שני קווים תחתונים) ולא מומלץ* ליצור Magic Methods חדשות.
* אמנם אין הגבלה טכנית על יצירת פונקציות שמתחילות ב – __
, אך יצירת פונקציות כאלו יוצרת סיכון להתנגשות עם גרסאות עתידיות של PHP.
Magic Methods מאפשרות לנו לשלוט על הדרך שבה מחלקה תתמודד עם אירוע מסויים, כמו קריאה לתכונה ($obj->property
), התייחסות לאובייקט כמחרוזת (echo $obj
) ועוד.
הן טומנות בחובן כוח גדול מאד – שימוש מושכל ונכון בהן יכול לחסוך לנו הרבה שורות קוד ולייעל את העבודה שלנו ואת הסקריפט. סקרנתי אתכם? בואו נצא לדרך ונסקור 7 Magic Methods:
__construct()
ו – __destruct()
(החל מגרסה 5)
אמרתי לכם שיצא לכם ליישם מתודת קסם!
כן כן, פונקציית הבנאי, שמבוצעת בעת יצירת עצם חדש, היא מתודת הקסם הנפוצה ביותר, חלקנו משתמשים בה מבלי לדעת שהיא כזאת.
בגרסה 4 ומטה, פונקציות בנאי הייתה פונקציה בעלת אותו שם כשם המחלקה, ולמרות שבגרסה 5 עדיין ניתן היה לרשום ככה, החל מגרסה 7 דרך הכתיבה הוצאה משימוש ובעתיד אף תימחק כליל).
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php class Foo { // PHP 4 and below, also supported in version 5. Depreceted in version 7 public function Foo() {...} // PHP 5 and above public function __construct() {...} } $f = new Foo(); // Constructor is executed |
האחות של __construct()
היא __destruct()
, שמבוצעת כאשר אין יותר התייחסות לאובייקט בקוד, כשהאובייקט נמחק או כשהסקריפט נגמר.
הפונקציה נותנת לנו הזדמנות ליצור תהליך מסודר של סיום השימוש באובייקט. שימוש נפוץ (אבל ממש לא רק) במחלקות מסד נתונים, שם משתמשים בפונקציה כדי לסגור את החיבור:
1 2 3 4 5 6 7 8 9 10 11 |
<?php class Foo { private $db; // instance of database class public function __construct() {...} public function __destruct() { $this->db->kill(); } } |
אבל עם כל הכבוד לבנאי ולהורס, באתם לפה כדי ללמוד דברים חדשים.
אז בואו נתקדם לכמה פונקציות פחות מוכרות..
__sleep()
ו – __wakeup()
(החל מגרסה 4)
המתודה __sleep
מבוצעת בעת קריאה לפונקציה serialize()
ולכן חשוב להבין מה הפונקציה עושה, כדי להבין את תפקיד המתודה:
הפונקציה serialize()
מקבלת פרמטר מכל סוג שהוא למעט Resource
ומחזירה ערך מומר למחרוזת שמייצגת את הפרמטר ומכילה בתוכה פירוט על סוג הנתונים והערכים שהפרמטר הכיל.
זו דרך נפוצה לשמור מידע באופן חסכוני. כך לדוגמה, ב – Codeigniter 2, נתוני ה – Session של כל משתמש עוברים סריאליזציה ונשמרים בדטבייס כמחרוזת.
ניקח את הדוגמה הבאה, שמתארת סריאליזציה של אובייקט מסוג DB
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php class Db { protected $connection; private $host, $username, $password; public function __construct() { $this->id = $id; $this->access = $access; $this->profile = $profile; } // ... } $u1 = new User(1, array('comment', 'post', 'moderate'), array('fullname' => 'Master Scripter')); var_dump(serialize($u1)); |
הערך שיודפס:
1 2 3 |
string(157) "O:4:"User":3:{s:2:"id";i:1;s:6:"access";a:3:{i:0;s:7:"comment";i:1;s:4:"post";i:2;s:8:"moderate";}s:7:"profile";a:1:{s:8:"fullname";s:15:"Master Scripter";}}" |
מטרתה של מתודת הקסם __sleep()
היא לקבוע אילו תכונות מחלקה אנחנו רוצים שיעברו בעת קריאה לפונקציה serialiaze()
,
והיא צריכה להחזיר מערך עם שמות התכונות שלפונקציה serialize
תהיה גישה אליהן.
לצורך הדוגמה, אנו רוצים להעביר אך ורק את ה – id
של המשתמש ואת ה – profile
שלו, ללא המערך שמפרט אילו הרשאות יש לו:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?php class User { public $id; public $access; public $profile; public function __construct($id, $access = array(), $profile = array()) { $this->id = $id; $this->access = $access; $this->profile = $profile; } public function __sleep() { return ['id', 'profile']; } // ... } $u1 = new User(1, array('comment', 'post', 'moderate'), array('fullname' => 'Master Scripter')); var_dump(serialize($u1)); |
הערך שיודפס:
1 2 3 |
string(86) "O:4:"User":2:{s:2:"id";i:1;s:7:"profile";a:1:{s:8:"fullname";s:15:"Master Scripter";}}" |
(לא באמת) קסם! ההתייחסות למערך access
נעלמה כלא הייתה 🙂
ואם serialize()
מפעילה את __sleep()
, אז מה קורה כשנקרא לפונקציה unserialize()
?
מתודת הקסם __wakeup()
תופעל
רק בשביל שאהיה רגוע שלכל הקוראים זה ברור – הפונקציה unserialize()
לוקחת מחרוזת שנוצרה מפונקציית serialize()
וממירה אותה בחזרה לערך PHP.
__toString()
מתודת קסם חביבה מאד, שמופעלת כאשר אנו מתייחסים למחלקה בהקשר של מחרוזת.
כדי להראות מה המתודה עושה, קבלו את המחלקה Student
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php class Student { protected $grades; public function __construct(Array $grades) { $this->grades = $grades; } public function setGrade($subject, $grade) { $this->grades['subject'][] = $grade; } } |
כאשר נתייחס למחלקה כמחרוזת, לדוגמה ננסה להדפיסה, נקבל את השגיאה הבאה:
Catchable fatal error: Object of class Student could not be converted to string
בואו נשדרג את המחלקה עם מתודת הקסם __toString
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?php class SubtleStudent extends Student { public function __toString() { // Output student's summed up average grade $sumAverage = 0; foreach($this->grades as $subject) $sumAverage += array_sum($subject) / sizeof($subject); return (String) ($sumAverage / sizeof($this->grades)); } } |
ועכשיו, במקום השגיאה הפטאלית ממקודם, נקבל הפעם את הציון המשוקלל של התלמיד:
1 2 3 4 5 6 7 8 9 10 11 |
<?php $grades = array( 'literature' => array(75,71,90,58), 'history' => array(66, 80, 100), 'mathematics' => array(91, 87, 79, 91), 'cooking' => array(100, 100, 100, 100) ); $childScripter = new SubtleStudent($grades); echo $childScripter // 85.625 |
שני קווים מנחים לשימוש בפונקציה:
- על הפונקציה להחזיר מחרוזת בלבד. במידה ואתם מעוניינים להדפיס ערך שהוא לא מחרוזת (כמו בדוגמה, שם הערך המודפס הוא בעצם מספר) יש להמירו למחרוזת
- לא ניתן להשתמש ב – Exceptions
העמסה
(באנגלית: overloading)
בשפות רבות שמיישמות תכנות מונחה עצמים, המושג העמסה משמעותו יצירת מס' מתודות בעלות שם זהה, אך חתימה שונה.
למשל, דוגמה מהשפה Java:
1 2 3 4 5 6 7 8 9 10 11 |
class Foo { public void myMethod(int arg1) { // ... } public void myMethod(int arg2, int arg2) { // ... } } |
בדוגמה הזו, קטע הקוד מאפשר לקרוא לפונקציה myMethod
גם עם פרמטר אחד וגם עם שניים, ולטפל באופן שונה בכל אחד מהמצבים.
לעומת זאת, ב – PHP המושג 'העמסה' מקבל משמעות שונה לגמרי, ומתייחס לפונקציות שמאפשרות לנו להגדיר תהליך לקריאת נתוני משתנים וקריאה לפונקציות באופן דינמי, או יצירת/מחיקת משתנים באופן דינמי.
מה הכוונה באופן דינמי? ניקח למשל את הדוגמה הבאה, שבה נוצר משתנה מחלקה באופן דינמי:
1 2 3 4 5 6 7 8 9 10 |
<?php class Foo { private $property; // ... } $myFoo = new Foo; $myFoo->myNewProperty = 'Hi!'; |
שתי מתודות הקסם הבאות עליהן אפרט 'רשומות' תחת המונח העמסה.
הפונקציות הללו נקראות (רק אם הוגדרו) כאשר נעשה שימוש בתכונות / פונקציות מחלקה לא זמינות*.
* הגדרה: תכונת / פונקציית מחלקה לא זמינה היא תכונה / פונקצייה שטרם הוכרזה או לא נגישה במרחב (scope) הנוכחי.
לדוגמה, קריאה לתכונת מחלקה מוגנת (protected
) מחוץ למחלקה:
1 2 3 4 5 6 7 8 9 10 |
<?php class Foo { protected property; // ... } $myFoo = new Foo; echo $myFoo->property; |
מתודות הקסם הבאות נחשבות למתודות העמסה, וככאלה נדרשות להיות מוגדרות כציבוריות (public
), ואין להעביר פרמטרים בהפניה (&
):
__callStatic()
(החל מגרסה 5.3.0) – מופעלת בקריאה לפונקציית מחלקה סטטית__call()
(החל מגרסה 5.0.0) – מופעלת בקריאה לפונקציה__isset()
(החל מגרסה 5.1.0) – מופעלת בקריאה לפונקציהisset
__unset()
(החל מגרסה 5.1.0) – מופעלת בקריאה לפונקציהunset
__set()
ו –__get()
– פירוט בפסקה הבאה
חשוב לי להביא את דעתי האישית – מתודות קסם בכלל והעמסה בפרט לא נדרשים עבור כל מחלקה.
סומך עליכם שתעשו שימוש מושכל ולא סתמי!
__set
ו – __get
מתודות הקסם נקראות כאשר מנסים ליישם (__set
) או לקרוא (__get
) ערך ל/מ תכונה לא זמינה.
שימוש בסיסי וקלאסי ב – __set
מתרחש במחלקה בה הוספת תכונות באופן דינמי הינה דבר שבשגרה,
וכדי למנוע התנגשות עם תכונות מחלקה מוגדרות מראש, ננתב את את כל התכונות שהוגדרו דינמית למערך ייעודי:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php class Foo { protected $dynamicProperties; public function __construct() { $this->dynamicProperties = array(); } public function __set($name, $value) { $this->dynamicProperties[$name] = $value; } } $foo = new Foo(); $foo->name = 'Master Scripter'; echo $foo->name; // Notice: Undefined property: Foo::$name |
שימו לב לאזהרה שקיבלנו: לא קיימת תכונה בשם $name
!
אבל אנחנו יודעים שהיא קיימת במערך $dynamicProperties
, שהוא מערך שלא ניתן לגשת אליו מחוץ למרחב המחלקה.
אז מה עושים? ניחשתם נכון! __get
בא לעזרתנו:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?php class Foo { protected $dynamicProperties; // ... public function __get($key) { if(array_key_exists($key, $this->dynamicProperties)) return $this->dynamicProperties[$key]; elseif(isset($this->$key)) return $this->$key; else return null; } } $foo = new Foo(); $foo->name = 'Master Scripter'; echo $foo->name; // Master Scripter |
עוד קצת על קסמים..
הפוסט הגיע לסיומו, אני מקווה שהעשרתי את הידע שלכם כמפתחי PHP ושמעתה תהפכו למאסטרים של Magic Methods.
במקורות (תכף, ממש מתחת) יש קישור לתיעוד הרשמי באתר של PHP, שם תוכלו לקרוא על כל מתודות הקסם הקיימות.
וכדי שלא ארגיש אשם על כך שיצאתם מפה ללא קסמים אמיתיים, אגלה לכם קסם שלא הרבה שמעו עליו. אבל תבטיחו לי שלא תגלו!
כל מי שמקליד את כתובת המייל שלו בטופס למטה, יקבל תכנים סופר-איכותיים ואקסקלוסיביים ישר לתיבת הדואר הנכנס!
שווה, לא?
מקורות
- רשימת פונקציות והסבר – תיעוד רשמי באתר השפה
- העמסה – תיעוד רשמי באתר השפה


7 תגובות
אהוד
13 בינואר 2017 - 13:59יפה מאד. תודה
אני באופן אישי מנסה לברוח מ PHP כ OOP.
זה היופי שבשפה הזו לדעתי – היכולת לכתוב קוד צד שרת בתכנות פרוצדורלי.
לא אכפת לי שהמגמה בעולם הפוכה.
יונתן נקסון
13 בינואר 2017 - 14:54בתור אחד שמתרחק מתכנות פרוצדורלי,
מעניין אותי לדעת האם ואיך אתה מצליח לשמור על סדר בקוד שלך?
מאיר
14 בינואר 2017 - 19:26סליחה אבל, אין לך מושג כמה אתה טועה…
אמיר סימן טוב
13 בינואר 2017 - 14:48כתבת יפה ומעניין. מוסבר היטב. אולי כדאי שתרחיב מעט על העמסה, רק החלק הזה לא ממש ברור.
יונתן נקסון
13 בינואר 2017 - 21:21קבל הרחבה בנושא העמסה.
האם עכשיו זה כתוב ברור יותר?
עידו
29 בספטמבר 2023 - 17:27תודה רבה מוסבר ממש טוב עזר לי מאדד
הדס
29 בספטמבר 2023 - 17:28תודה רבה מוסבר ממש טוב עזר לי מאדדד