有了前面的三篇铺垫,现在可以整体过下SugarAutoLoader::init()方法了。
public static function init() { $config = SugarConfig::getInstance(); /* * 在开发模式下,删除./cache/目录下的file_map.php和class_map.php * 每次在fileExists($filename)时,会直接判断文件是否存在 * 而不是一开始读取file_map.php后放入$classMap中 * When development mode is enabled, we bypass the usage * of the filemap and build the classmap dynamically on * every page load. We drop both cache file to make sure * when devMode is disabled again that the system is * properly initialized again withour the need for * running a QuickRepairRebuild. */ self::$devMode = $config->get('developerMode', false); if (self::$devMode) { @unlink(sugar_cached(self::CACHE_FILE)); @unlink(sugar_cached(self::CLASS_CACHE_FILE)); } // Extensions included from config // 可以从配置文件里添加需要sugarcrm允许的后缀 $exts = $config->get('autoloader.exts'); if (is_array($exts)) { self::$exts += $exts; } // Excludes from config // 可以从配置文件里添加需要跳过的配置 $exclude = $config->get('autoloader.exclude'); if (is_array($exclude)) { self::$exclude += $exclude; } // Create file map // 创建./include/file_map、./include/class_map.php self::loadFileMap(); // 加载composer引用的类【命名空间】 // Composer integration self::loadComposerIncludePaths(); self::loadComposerPsr4(); self::loadComposerPsr0(); // Build class map (implicitly includes Composer's classmap) // 看了几遍代码后,仍是没有发现隐式包含的composer类【self::loadFileMap()已经都包含】 self::loadClassMap(); // Register ourself (prepend) // 注册SugarCRM自己的autoload方法,这里true说明,每次自动加载把要加载类提到栈顶 self::registerAutoload(true); // Load extensions // 加载模块扩展 self::loadExts(); /* protected static function loadExts() { include "ModuleInstall/extensions.php"; self::$extensions = $extensions; } */ }
public static function init() { ``` // 注册SugarCRM自己的autoload方法 registerAutoload(true); ``` } public static function registerAutoload($prepend = true) { spl_autoload_register(array('SugarAutoLoader', 'autoload'), true, $prepend); } // 注意self::$classMapDirty变量,当./cache/class_map.php生成后,此变量为false // 但是加载相关类后,会置为true,在项目结束时,调用sugar_cleanup中的 // SugarAutoLoader::saveClassMap()方法会再次生成./cache/class_map.php文件 // 这里有一条比较重要if (empty(self::$moduleMap)),这个判断会引用项目中二次开发的模块 public static function autoload($class) { // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 $class = ltrim($class, '\\'); $uclass = ucfirst($class); /* * 在此数组中的类不加载 public static $noAutoLoad = array( 'Tracker' => true, // this one is generated by ViewFactory for classic view, but if never actually exists 'UsersViewClassic' => true, ); */ if (!empty(self::$noAutoLoad[$class])) { return false; } // try known classes // 加载class_map.php中的类 if (isset(self::$classMap[$uclass])) { if (self::$classMap[$uclass]) { // No need for a file_exists, if it is in the map we have found it before // 这里不用再判断文件是否存在,因为这个已经在前面发现了 require_once self::$classMap[$uclass]; return true; } // 在class_map.php中,如果文件不存在,value会是false,因此会走到此步 return false; } // try namespaces // 使用命名空间,这里会用到init里composer集成的数据 // 优先加载psr-4,然后按照路径从多到少匹配 /* public static function getFilenameForFQCN($class) { // PSR-4 has precedence if ($file = self::getFileNamePsr4($class)) { return $file; } return self::getFileNamePsr0($class); } #[ # 'Sugarcrm\\Sugarcrm\\inc\\' => [ # '/custom/src', # '/custom/', # ] #] public static function getFileNamePsr4($class) { foreach (self::$namespaceMapPsr4 as $prefix => $paths) { foreach ($paths as $path) { if (strpos($class, $prefix) === 0) { $path = empty($path) ? '' : $path . '/'; $path .= str_replace('\\', '/', str_replace($prefix, '', $class)) . '.php'; if (self::fileExists($path)) { return $path; } } } } return false; } public static function getFileNamePsr0($class) { foreach (self::$namespaceMap as $namespace => $paths) { foreach ($paths as $path) { if (strpos($class, $namespace) === 0) { $path = empty($path) ? '' : $path . '/'; if (false !== $pos = strrpos($class, '\\')) { $path .= str_replace('\\', '/', substr($class, 0, $pos)) . '/'; $path .= str_replace('_', '/', substr($class, $pos + 1)) . '.php'; } else { $path .= str_replace('_', '/', $class) . '.php'; } if (self::fileExists($path)) { return $path; } } } } return false; } public static function load($file) { if (self::fileExists($file)) { require_once $file; return true; } return false; } */ if (false !== strpos($class, '\\')) { if ($file = self::getFilenameForFQCN($class)) { if (self::load($file)) { self::$classMap[$class] = $file; self::$classMapDirty = true; return true; } } self::$classMap[$class] = false; self::$classMapDirty = true; return false; } // 首次会加载include/modules.php文件 // 此文件定义了页面tap显示顺序、应用的模块列表【$beanList】 // 安全设置、管理员独有的权限(all)…… if (empty(self::$moduleMap)) { if (isset($GLOBALS['beanFiles'])) { self::$moduleMap = $GLOBALS['beanFiles']; } else { include('include/modules.php'); /* foreach(SugarAutoLoader::existing('include/modules_override.php', SugarAutoLoader::loadExtension("modules")) as $modExtFile) { include $modExtFile; } // 这一步会加载self::loadExts()引入的模块数组self::$extensions public static function loadExtension($extname, $module = "application") { // self::$extensions['modules'] = // array("section" => "beans", "extdir" => "Include", "file" => 'modules.ext.php', "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") { // custom/application/Ext/Include/modules.ext.php // 此文件是自动生成的,里面存放着二次开发的模块 // 如,ca_capital,投资交易,url为http://sugar/#ca_capital $file = "custom/application/Ext/{$ext["extdir"]}/{$ext["file"]}"; } else { // custom/modules/application/Ext/Include/modules.ext.php $file = "custom/modules/{$module}/Ext/{$ext["extdir"]}/{$ext["file"]}"; } if (self::fileExists($file)) { return $file; } return false; } */ self::$moduleMap = $beanFiles; } } // Try known modules // 引入上一步的模块映射 if (!empty(self::$moduleMap[$class])) { require_once self::$moduleMap[$class]; return true; } // HTMLPurifier为PHP富文本HTML过滤器了,采用了白名单机制 // 有效杜绝了用户提交表单中的非法HTML标签,从而可以防止XSS攻击 // ./vendor/HTMLPurifier if (strncmp('HTMLPurifier', $class, 12) == 0) { return HTMLPurifier_Bootstrap::autoload($class); } // Split on _, capitalize elements and make a path // foo_bar -> Foo/Bar. $class_file = join('/', array_map('ucfirst', explode('_', $class))); // Try known prefixes /* public static $prefixMap = array( 'SugarACL' => "data/acl/", 'SugarWidget' => "include/generic/SugarWidgets/", 'Zend_' => 'vendor/', 'SugarJob' => 'include/SugarQueue/jobs/', 'MetaDataContext' => 'modules/ModuleBuilder/parsers/MetaDataContext/', ); */ foreach (self::$prefixMap as $prefix => $dir) { if (strncasecmp($prefix, $class, strlen($prefix)) === 0) { if ($file = self::requireWithCustom("{$dir}$class_file.php")) { self::$classMap[$uclass] = $file; self::$classMapDirty = true; return true; } else { break; } } } // 下面的几个判断是页面元素展现相关类 // Special cases // Special case because lookup goes to $_REQUEST['module'] /* protected static function getFilenameForViewClass($class) { $module = false; if (!empty($_REQUEST['module']) && substr($class, 0, strlen($_REQUEST['module'])) === $_REQUEST['module']) { //This is a module view $module = $_REQUEST['module']; $class = substr($class, strlen($module)); } if (substr($class, 0, 4) == "View") { $view = strtolower(substr($class, 4)); if ($module) { return self::requireWithCustom("modules/$module/views/view.$view.php"); } else { return self::requireWithCustom("include/MVC/View/views/view.$view.php"); } } return false; } */ if ($file = self::getFilenameForViewClass($class)) { self::$classMap[$uclass] = $file; self::$classMapDirty = true; return true; } // Special case because widget name can be lowercased /* protected static function getFilenameForSugarWidget($class) { //Only bother to check if the class name starts with SugarWidget if (strpos($class, 'SugarWidgetField') !== false) { //We need to lowercase the portion after SugarWidgetField $name = substr($class, 16); if (empty($name)) { return false; } $class = 'SugarWidgetField' . strtolower($name); return self::requireWithCustom("include/generic/SugarWidgets/{$class}.php"); } return false; } */ if ($file = self::getFilenameForSugarWidget($class)) { self::$classMap[$uclass] = $file; self::$classMapDirty = true; return true; } // Special case because it checks by ending in Layout /* protected static function getFilenameForLayoutClass($class) { if (substr($class, -6) == "Layout") { return self::requireWithCustom("include/MetaDataManager/layouts/$class.php"); } return false; } */ if ($file = self::getFilenameForLayoutClass($class)) { self::$classMap[$uclass] = $file; self::$classMapDirty = true; return true; } /* protected static function getFilenameForExpressionClass($class) { if (substr($class, -10) == 'Expression') { if ($file = self::requireWithCustom("include/Expressions/Expression/{$class}.php")) { return $file; } $types = array("Boolean", "Date", "Enum", "Generic", "Numeric", "Relationship", "String", "Time"); foreach ($types as $type) { if ($file = self::requireWithCustom("include/Expressions/Expression/{$type}/{$class}.php")) { return $file; } } } return false; } */ if ($file = self::getFilenameForExpressionClass($class)) { self::$classMap[$uclass] = $file; self::$classMapDirty = true; return true; } // Try known dirs /* public static $dirMap = array( 'clients/base/api/', "data/visibility/", "data/Relationships/", "data/duplicatecheck/", 'include/api/', "include/CalendarEvents/", "include/SugarSearchEngine/", "include/", "modules/Mailer/", 'modules/Calendar/', ); */ foreach (self::$dirMap as $dir) { // include/Class.php if ($file = self::requireWithCustom("{$dir}$class_file.php")) { self::$classMap[$uclass] = $file; self::$classMapDirty = true; return true; } // include/Class/Class.php // Note here we don't use $class_file since using path twice would not make sense: // Foo/Bar/Foo/Bar.php vs. Foo_Bar/Foo_Bar.php if ($file = self::requireWithCustom("{$dir}$class/$class.php")) { self::$classMap[$uclass] = $file; self::$classMapDirty = true; return true; } // try include/Foo_Bar.php as a last resort if ($file = self::requireWithCustom("{$dir}$class.php")) { self::$classMap[$uclass] = $file; self::$classMapDirty = true; return true; } } self::$classMap[$uclass] = false; self::$classMapDirty = true; return false; }
分析完了init,基本上auotoloader.php文件也大体结束了,剩余的就是相关引入文件操作了,SugarCRM里也有相关简短说明,可以看看。