本篇主要分析下SugarCRM的钩子源码实现,即使一开始对SugarCRM不熟,掌握了钩子的用法,也能写写相关的功能。
// 调用钩子 LogicHook::initialize()->call_custom_logic('', 'entry_point_variables_setting'); // 返回实例 // ./include/utils/LogicHook.php static function initialize() { if (empty($GLOBALS['logic_hook'])) $GLOBALS['logic_hook'] = new LogicHook(); return $GLOBALS['logic_hook']; } // 调用钩子,$module_dir为空调用application,不为空调用module // 当$module_dir为空时,$this->bean为null function call_custom_logic($module_dir, $event, $arguments = array()) { // $this->bean默认为null // 如果传过来的$module_dir为空,那么下面的BeanFactory::getBean($module_dir)返回的还是null /* // ./data/SugarBean.php public static function getBean($module, $id = null, $params = array(), $deleted = true) { ``` $beanClass = self::getBeanName($module); if (empty($beanClass) || !class_exists($beanClass)) return null; ``` } // 获取bean名称,如果为空,返回false // 否则返回已有的$bean_classes // 或是$beanList里的类【该数组在./include/modules.php里】 public static function getBeanName($module) { if(!empty(self::$bean_classes[$module])) { return self::$bean_classes[$module]; } global $beanList; if (empty($beanList[$module])) { return false; } return $beanList[$module]; } */ $origBean = $this->bean; if ($origBean === null) { $bean = BeanFactory::getBean($module_dir); if ($bean instanceOf SugarBean) { $this->setBean($bean); } } // declare the hook array variable, it will be defined in the included file. // 写过钩子都是通过此数组定义 $hook_array = null; // 看到上篇日志管理那篇博客应该知道下面debug方法不会执行 // 除非在config.php把日志级别配置为debug if (isset($GLOBALS['log'])) { $GLOBALS['log']->debug("Hook called: $module_dir::$event"); } // 如果$module_dir不为空,那么调用的是模块钩子 // 这里的模块,就是页面导航栏中的具体菜单项 if (!empty($module_dir)) { // This will load an array of the hooks to process $hooks = $this->getHooks($module_dir); if (!empty($hooks)) { $this->process_hooks($hooks, $event, $arguments); } } // 否则调用的是应用钩子 // 获取全部的应用钩子 $hooks = $this->getHooks(''); if (!empty($hooks)) { // 执行钩子逻辑 $this->process_hooks($hooks, $event, $arguments); } // 如果$module_dir为空,$origBean便为null // 那么就是把$this->bean置为null if ($origBean === null) { $this->setBean($origBean); } } // 获取钩子 public function getHooks($module_dir, $refresh = false) { if ($refresh || !isset(self::$hooks[$module_dir])) { self::$hooks[$module_dir] = $this->loadHooks($module_dir); } return self::$hooks[$module_dir]; } // 在指定文件中加载钩子 public function loadHooks($module_dir) { $hook_array = array(); if (!empty($module_dir)) { $custom = "custom/modules/$module_dir"; } else { $custom = "custom/modules"; } // 这一步可以看出当$module_dir为空时,查找的是application的钩子文件 // 否则查找的是指定模块的钩子文件 foreach (SugarAutoLoader::existing( "$custom/logic_hooks.php", SugarAutoLoader::loadExtension("logichooks", empty($module_dir) ? "application" : $module_dir) ) as $file) { if (isset($GLOBALS['log'])) { $GLOBALS['log']->debug('Including hook file: ' . $file); } include $file; } return $hook_array; } // ./include/utils/autoloader.php // self::$extensions存储的是./ModuleInstall.php中的数据,在SugarAutoLoader::init()中加载执行 // 执行完此方法后,返回的是 custom/modules/application/Ext/LogicHoooks/logichooks.ext.php文件 public static function loadExtension($extname, $module = "application") { if (empty(self::$extensions[$extname])) return false; $ext = self::$extensions[$extname]; if (empty($ext['file']) || empty($ext['extdir'])) { // custom rebuilds, can't handle return false; } if (isset($ext["module"])) { $module = $ext["module"]; } if ($module == "application") { $file = "custom/application/Ext/{$ext["extdir"]}/{$ext["file"]}"; } else { $file = "custom/modules/{$module}/Ext/{$ext["extdir"]}/{$ext["file"]}"; } if (self::fileExists($file)) { return $file; } return false; } // 执行钩子逻辑 /* Array ( [before_save] => Array ( [0] => Array ( [0] => 1 [1] => BeforeSaveHooksMethod [2] => custom/modules/ptsdb_double_points_rule/DoublePointsRuleHooks.php [3] => DoublePointsRuleHooks [4] => beforeSaveHooksMethod ) ) [after_save] => Array ( [0] => Array ( [0] => 1 [1] => AfterSaveHooksMethod [2] => custom/modules/ptsdb_double_points_rule/DoublePointsRuleHooks.php [3] => DoublePointsRuleHooks [4] => afterSaveHooksMethod ) ) [after_delete] => Array ( [0] => Array ( [0] => 1 [1] => AfterDeleteHooksMethod [2] => custom/modules/ptsdb_double_points_rule/DoublePointsRuleHooks.php [3] => DoublePointsRuleHooks [4] => afterDeleteHooksMethod ) ) ) */ function process_hooks($hook_array, $event, $arguments) { // 如果已经定义了相关的钩子,那么在这就可以开始执行了 // 钩子是按照index从小到大的次序来执行的 // Now iterate through the array for the appropriate hook if (!empty($hook_array[$event])) { // Apply sorting to the hooks using the sort index. // Hooks with matching sort indexes will be processed in no particular order. $sorted_indexes = array(); foreach ($hook_array[$event] as $idx => $hook_details) { $order_idx = $hook_details[0]; $sorted_indexes[$idx] = $order_idx; } asort($sorted_indexes); $process_order = array_keys($sorted_indexes); foreach ($process_order as $hook_index) { $hook_details = $hook_array[$event][$hook_index]; // 如果钩子执行文件不存在,记日志报错【不会执行】,进入下一次循环 if (!file_exists($hook_details[2])) { if (isset($GLOBALS['log'])) { $GLOBALS['log']->error('Unable to load custom logic file: ' . $hook_details[2]); } continue; } // 加载钩子执行文件 include_once($hook_details[2]); // 钩子执行类 $hook_class = $hook_details[3]; // 钩子执行方法 $hook_function = $hook_details[4]; // Make a static call to the function of the specified class // TODO Make a factory for these classes. Cache instances accross uses // 如果类和方法一样,直接new钩子类,把钩子方法及参数传进去 if ($hook_class == $hook_function) { if (isset($GLOBALS['log'])) { $GLOBALS['log']->debug('Creating new instance of hook class ' . $hook_class . ' with parameters'); } // $this->bean不为空,大都是数据库相关的操作 类 if (!is_null($this->bean)) $class = new $hook_class($this->bean, $event, $arguments); // 不是的话,就不用传 else $class = new $hook_class($event, $arguments); // 类和方法不同名 }else { if (isset($GLOBALS['log'])) { $GLOBALS['log']->debug('Creating new instance of hook class ' . $hook_class . ' without parameters'); } // 实例化钩子类 $class = new $hook_class(); // 执行钩子 if (!is_null($this->bean)) { $callback = array($class, $hook_function); // & is here because of BR-1345 and old broken hooks // that use &$bean in args. // 这里主要是兼容以前版本的写法,基本上$params还是&$this->bean, $event, $arguments $params = array_merge(array(&$this->bean, $event, $arguments), array_slice($hook_details, 5)); call_user_func_array($callback, $params); } else $class->$hook_function($event, $arguments); } } } }