הפעם נצלול אל הקוד של Mage::dispatchEvent ונבין את החלקים החשובים במימוש ה-Observer Pattern במג'נטו 1. שימו לב שזה הפוסט האחרון על מג'נטו בגרסה 1, מטעמים פרקטיים החל מהפוסט הבא נעסוק בגרסה 2 בלבד.
בפעם שעברה ראינו את הקריאה ל-Mage::dispatchEvent והפעם נבחן את הקוד עצמו:
public static function dispatchEvent($name, array $data = array())
{
Varien_Profiler::start('DISPATCH EVENT:'.$name);
$result = self::app()->dispatchEvent($name, $data);
Varien_Profiler::stop('DISPATCH EVENT:'.$name);
return $result;
}
כפי שניתן לראות, הפונקציה בסך הכל עוטפת פונקציה באותו השם שקיימת תחת Mage_Core_Model_App.
הפונקציה שנמצאת תחת Mage_Core_Model_App מתחילה כך:
public function dispatchEvent($eventName, $args)
{
$eventName = strtolower($eventName);
foreach ($this->_events as $area=>$events) {
בשלב הראשון שם האירוע שהעברנו לפונקציה מומר לאותיות קטנות. לאחר מכן מתחיל מעבר על מערך אסוציאטיבי שנקרא $_events. מערך זה משמש כ-cache של המחלקה לאירועים. המערך כולל רשימת אזורים של מג'נטו כמפתחות, כאשר כל מפתח מפנה למערך אסוציאטיבי עם שמות אירועים כמפתחות, וכל אירוע במערך יכול להכיל מערך של observers הכולל את המחלקות והפונקציות שיש להריץ בתגובה לאירוע מסויים. כאשר מג'נטו מתחילה לרוץ המערך נראה כך:
array (size=1)
'global' =>
array (size=0)
empty
האזור global הוא המפתח היחיד שנמצא בו, ומצביע למערך ריק. כשמג'נטו תסיים לרוץ המערך יכלול גם את האזור שאנחנו נמצאים בו (frontend, adminhtml, crontab) ואת פרטי האירועים שמשוייכים לאותו אזור. הסיבה בגינה רק global מצוי במערך ההתחלתי, היא שהוא נוצר ממש עם תחילת הרצת האפליקציה של מג'נטו, כך שהאזור בו אנחנו נמצאים עדיין לא נקבע.
המערך לאחר סיום הריצה של מג'נטו (חלקי):
array (size=2)
'global' =>
array (size=43)
'resource_get_tablename' => boolean false
'controller_front_init_routers' =>
array (size=1)
'observers' =>
array (size=1)
...
'model_load_after' => boolean false
'controller_action_predispatch' =>
array (size=1)
'observers' =>
array (size=1)
...
'frontend' =>
array (size=52)
'controller_action_predispatch' =>
array (size=1)
'observers' =>
array (size=7)
...
'model_save_before' => boolean false
בהמשך הפונקציה, בתוך לולאת ה-foreach, נמצא את הקוד הבא:
if (!isset($events[$eventName])) {
$eventConfig = $this->getConfig()->getEventConfig($area, $eventName);
if (!$eventConfig) {
$this->_events[$area][$eventName] = false;
continue;
}
$observers = array();
foreach ($eventConfig->observers->children() as $obsName=>$obsConfig) {
$observers[$obsName] = array(
'type' => (string)$obsConfig->type,
'model' => $obsConfig->class ? (string)$obsConfig->class : $obsConfig->getClassName(),
'method'=> (string)$obsConfig->method,
'args' => (array)$obsConfig->args,
);
}
$events[$eventName]['observers'] = $observers;
$this->_events[$area][$eventName]['observers'] = $observers;
}
מג'נטו בודקת האם שם האירוע שלו אנחנו מעוניינים לעשות dispatch כבר קיים במערך ה-cache, תחת רשימת שמות האירועים של האזור הנוכחי. במידה ולא, מג'נטו תוסיף מפתח עם שם האירוע למערך. בנוסף לכך, היא תיגש לאובייקט הקונפיגורציה, Mage_Core_Model_Config, שמכיל מידע מכל קבצי ה-XML במערכת, ותבדוק אם קיימות הגדרות מתאימות. במידה ואלו קיימות, מג'נטו תוסיף את המידע תחת שם האירוע עם מפתח בשם observers שמצביע למערך.
דוגמה למערך של observers:
array (size=1)
'observers' =>
array (size=7)
'controller_action_predispatch_observer' =>
array (size=4)
'type' => string '' (length=0)
'model' => string 'produckt_observertutorial/observer' (length=34)
'method' => string 'controllerActionPredispatch' (length=27)
'args' =>
array (size=0)
...
אנחנו יכולים לראות שלכל observer יש מזהה ייחודי, אותו הגדרנו ב-config.xml והוא מחזיק מערך הכולל 4 מפתחות, type, model, method ו-args. המפתחות model ו-method מציינים את המחלקה שלנו והפונקציה שברצוננו להריץ בהתאמה. אל args ו-type נגיע בהמשך.
הקוד בלולאה ממשיך כך:
if (false===$events[$eventName]) {
continue;
} else {
$event = new Varien_Event($args);
$event->setName($eventName);
$observer = new Varien_Event_Observer();
}
כאן מג'נטו בודקת אם יש observers שמשוייכים לאירוע, במידה וכן היא תיצור שני אובייקטים חדשים, אחד מסוג Varien_Event והשני מסוג Varient_Event_Observer. שימו לב ש-Varien_Event מקבל את מערך המידע שצרפנו בקריאה ל-dispatchEvent והבנאי שלו ידאג שהמידע הזה יהפוך ל-data של האובייקט (Varien_Event מרחיבה את Varien_Object וזו ההתנהגות של הבנאי שלו בעצם). מג'נטו גם קוראת ל-setName על Varien_Event כך שבהמשך נוכל לקבל את שם האירוע מהפונקציה ב-observer שלנו.
ולקראת הסוף, הגענו לחלק החשוב ביותר בלולאה:
foreach ($events[$eventName]['observers'] as $obsName=>$obs) {
$observer->setData(array('event'=>$event));
Varien_Profiler::start('OBSERVER: '.$obsName);
switch ($obs['type']) {
case 'disabled':
break;
case 'object':
case 'model':
$method = $obs['method'];
$observer->addData($args);
$object = Mage::getModel($obs['model']);
$this->_callObserverMethod($object, $method, $observer);
break;
default:
$method = $obs['method'];
$observer->addData($args);
$object = Mage::getSingleton($obs['model']);
$this->_callObserverMethod($object, $method, $observer);
break;
}
Varien_Profiler::stop('OBSERVER: '.$obsName);
}
כאן מג'נטו עוברת על רשימת ה-observers שמשוייכים לאירוע, היא בודקת מה ה-type של ה-observer ולפי ה-type בוחרת באחת משלוש דרכי טיפול. איפה ה-type מוגדר? ב-config.xml, יחד עם שאר ההגדרות של ה-observer שלכם. פעמים רבות תראו observer מוגדר ב-config.xml באופן הבא:
<controller_action_predispatch_observer>
<type>singleton</type>
<class>produckt_observertutorial/observer</class>
<method>controllerActionPredispatch</method>
</controller_action_predispatch_observer>
מה זה אומר?
בקוד אנחנו יכולים לראות 3 מקרים שמצויינים באופן מפורש: disabled, object ו-model.
אם ה-type של ה-observer מוגדר כ-disabled, הקוד שלנו לא יופעל כלל.
model ו-object מטופלים באופן זהה, מג'נטו תיצור מופע חדש של המחלקה שלנו עם getModel ותקרא לפונקציה שהגדרנו.
ומה סוג הטיפול השלישי? אם תחת type לא ציינו דבר, או כתבנו כל דבר פרט ל-disabled, object או model, מג'נטו תיצור מופע של המחלקה שלנו עם getSingleton. המשמעות היא שבמידה והקוד שלנו נקרא פעמים רבות במהלך הריצה, רק מופע אחד של המחלקה שלנו יווצר במקום מופע חדש לכל קריאה. אז למה ב-xml הוספנו את תגית ה-type עם הערך singleton במקום להשמיט אותה? מוסכמות.
אבל רגע, ראינו שלכל observer יש 4 מפתחות, type, model, method ו-args. מה עושה args? ובכן, נכון לרגע זה, כלום. מאחר וסוף החיים של מג'נטו 1 מתקרב, סביר להניח שלעולם לא נדע למה התכוון המשורר.
וטיפ לסיום. למטרות דיבוג של observers אפשר להשתמש ב-n98-magerun באופן הבא:
n98magerun.phar dev:module:observer:list
ואז, במקום לחפור ב-var_dump של מערך ה-$_events למשל, תוכלו פשוט להסתכל על טבלה ובה רשימת האירועים וה-observers שרשומים אליהם.
+-------------------------------------------------------------+------------------------------------------------------------------------------------+ | Event | Observers | +-------------------------------------------------------------+------------------------------------------------------------------------------------+ | controller_action_layout_load_before | singleton customer/observer::beforeLoadLayout | | | singleton persistent/observer::createPersistentHandleLayout | | sales_model_service_quote_submit_after | singleton customer/observer::quoteSubmitAfter | | controller_action_noroute | singleton cms/observer::noRoute | | controller_action_nocookies | singleton cms/observer::noCookies | | customer_login | model catalog/product_compare_item::bindCustomerLogin | | | singleton checkout/observer::loadCustomerQuote | | | singleton log/visitor::bindCustomerLogin | | | singleton reports/event_observer::customerLogin | | | singleton wishlist/observer::customerLogin |
ובפעם הבאה, מתחילים עם מג'נטו 2!
