החל מגרסה 5, PHP מציעה תמיכה מלאה-יותר (אך לא מלאה) של עקרונות תכנות מונחה עצמים,
כשאחת התכונות החדשות שנוספו היא 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 דרך הכתיבה הוצאה משימוש ובעתיד אף תימחק כליל).
<?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()
, שמבוצעת כאשר אין יותר התייחסות לאובייקט בקוד, כשהאובייקט נמחק או כשהסקריפט נגמר.
הפונקציה נותנת לנו הזדמנות ליצור תהליך מסודר של סיום השימוש באובייקט. שימוש נפוץ (אבל ממש לא רק) במחלקות מסד נתונים, שם משתמשים בפונקציה כדי לסגור את החיבור:
<?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
:
<?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));
הערך שיודפס:
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
שלו, ללא המערך שמפרט אילו הרשאות יש לו:
<?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));
הערך שיודפס:
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
:
<?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
:
<?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));
}
}
ועכשיו, במקום השגיאה הפטאלית ממקודם, נקבל הפעם את הציון המשוקלל של התלמיד:
<?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
שני קווים מנחים לשימוש בפונקציה:
(באנגלית: overloading)
בשפות רבות שמיישמות תכנות מונחה עצמים, המושג העמסה משמעותו יצירת מס' מתודות בעלות שם זהה, אך חתימה שונה.
למשל, דוגמה מהשפה Java:
class Foo {
public void myMethod(int arg1) {
// ...
}
public void myMethod(int arg2, int arg2) {
// ...
}
}
בדוגמה הזו, קטע הקוד מאפשר לקרוא לפונקציה myMethod
גם עם פרמטר אחד וגם עם שניים, ולטפל באופן שונה בכל אחד מהמצבים.
לעומת זאת, ב – PHP המושג 'העמסה' מקבל משמעות שונה לגמרי, ומתייחס לפונקציות שמאפשרות לנו להגדיר תהליך לקריאת נתוני משתנים וקריאה לפונקציות באופן דינמי, או יצירת/מחיקת משתנים באופן דינמי.
מה הכוונה באופן דינמי? ניקח למשל את הדוגמה הבאה, שבה נוצר משתנה מחלקה באופן דינמי:
<?php
class Foo {
private $property;
// ...
}
$myFoo = new Foo;
$myFoo->myNewProperty = 'Hi!';
שתי מתודות הקסם הבאות עליהן אפרט 'רשומות' תחת המונח העמסה.
הפונקציות הללו נקראות (רק אם הוגדרו) כאשר נעשה שימוש בתכונות / פונקציות מחלקה לא זמינות*.
* הגדרה: תכונת / פונקציית מחלקה לא זמינה היא תכונה / פונקצייה שטרם הוכרזה או לא נגישה במרחב (scope) הנוכחי.
לדוגמה, קריאה לתכונת מחלקה מוגנת (protected
) מחוץ למחלקה:
<?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
מתרחש במחלקה בה הוספת תכונות באופן דינמי הינה דבר שבשגרה,
וכדי למנוע התנגשות עם תכונות מחלקה מוגדרות מראש, ננתב את את כל התכונות שהוגדרו דינמית למערך ייעודי:
<?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
בא לעזרתנו:
<?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 מתודות קסם ששווה להכיר הופיע ראשון בMasterScripter