在SugarCRM中的cache目录有两个文件file_map.php和class_map.php,这两文件,在生产模式下(配置文件中developerMode为false),如果没有file_map.php或是为空,则会生成两文件,下面是流程。
// init() ---- > self::loadFileMap(); public static function loadFileMap() { $existing_files = array(); /* * 在开发模式下,会删除file_map.php、class_map.php * 生产模式下,若手动删除file_map.php则会扫描整个项目再重新生成 * In development mode we can skip loading/building * the file_map as we will revert back to pure file_exists * and not make use of this mapping. */ if (!self::$devMode) { // 若是有此文件,则$existing_files数组里有元素 @include sugar_cached(self::CACHE_FILE); // 没有的话,便重新生成,并引入 // 同时也会生成./cache/class_map.php,这个在应用结束时也会生成【register_shutdown_function('sugar_cleanup');】 // self::$classMapDirty在每次生成文件[class_map.php]后会置为false /* SugarAutoLoader::saveClassMap(); public static function saveClassMap() { if (!self::$devMode && self::$classMapDirty && !empty(self::$classMap)) { write_array_to_file("class_map", self::$classMap, sugar_cached(self::CLASS_CACHE_FILE)); self::$classMapDirty = false; } } */ if (empty($existing_files)) { // oops, something happened to cache // try to rebuild self::buildCache(); @include sugar_cached(self::CACHE_FILE); } } self::$filemap = $existing_files; self::$memmap = array(); } // ./include/utils/autoloader.php // $filemap 存放的是系统内允许后缀的所有文件 // $memmap 存放的是$filemap文件内的类 public static function buildCache() { // 这里只会收录self::$ext里允许的后缀文件 $data = self::scanDir(""); // 写./include/file_map.php write_array_to_file("existing_files", $data, sugar_cached(self::CACHE_FILE)); self::$filemap = $data; self::$memmap = array(); // Rebuild the class cache so that it can find any new classes in the file map // 写./include/class_map.php,不过应用结束后,还会写一次,有性能问题 self::buildClassCache(); } // ./include/utils/file_utils.php // write_array_to_file("existing_files", $data, sugar_cached(self::CACHE_FILE)); function write_array_to_file($the_name, $the_array, $the_file, $mode="w", $header='' ) { if(!empty($header) && ($mode != 'a' || !file_exists($the_file))){ $the_string = $header; }else{ $the_string = "<?php\n" . '// created: ' . date('Y-m-d H:i:s') . "\n"; } $the_string .= "\$$the_name = " . var_export_helper( $the_array ) . ";"; if(sugar_file_put_contents_atomic($the_file, $the_string) !== false) { if(substr($the_file, 0, 7) === 'custom/') { // record custom writes to file map SugarAutoLoader::addToMap($the_file); } return true; } return false; } // ./include/utils/sugar_file_utils.php // sugar_file_put_contents_atomic($the_file, $the_string) function sugar_file_put_contents_atomic($filename, $data, $mode='wb', $use_include_path=false, $context=null) { $dir = dirname($filename); // 在$dir目录创建个唯一的临时文件 $temp = tempnam($dir, 'temp'); // 这里两层是为了在外层失败的情况下,再写入一次,若还是失败,跑出错误,便返回 if (!($f = @fopen($temp, $mode))) { $temp = $dir . DIRECTORY_SEPARATOR . uniqid('temp'); if (!($f = @fopen($temp, $mode))) { trigger_error("sugar_file_put_contents_atomic() : error writing temporary file '$temp'", E_USER_WARNING); return false; } } fwrite($f, $data); fclose($f); // 这里也是,尝试两次重命名,若还是失败,删除临时文件,跑出错误 if (!@rename($temp, $filename)) { @unlink($filename); if (!@rename($temp, $filename)) { // cleaning up temp file to avoid filling up temp dir @unlink($temp); trigger_error("sugar_file_put_contents_atomic() : fatal rename failure '$temp' -> '$filename'", E_USER_ERROR); } } if(file_exists($filename)) { // Add to the file loader cache // 如果此文件不存在explode中,则会添加到self::$filemap数组中 if (!SugarAutoLoader::fileExists($filename)) { SugarAutoLoader::addToMap($filename); } return sugar_chmod($filename); } return false; } // ./include/utils/sugar_file_utils.php // 如果是windows直接返回true,否则试图更改$filename的mode值 function sugar_chmod($filename, $mode=null) { if(!is_windows()){ if ($mode === null) { $mode = get_mode('file_mode', 0660); } if(isset($mode) && $mode > 0){ return @chmod($filename, $mode); }else{ return false; } } return true; } // ./include/utils/sugar_file_utils.php // 如果是windows返回传过去的mode值,不是的话返回配置文件中的值 function get_mode($key = 'dir_mode', $mode=null) { if ( !is_int($mode) ) $mode = (int) $mode; if(!class_exists('SugarConfig', true)) { require 'include/SugarObjects/SugarConfig.php'; } if(!is_windows()){ $conf_inst=SugarConfig::getInstance(); $mode = $conf_inst->get('default_permissions.'.$key, $mode); } return $mode; } // ./include/utils/autoloader.php public static function buildClassCache() { $class_map = array(); // 加载核心映射类,此文件存在的话返回$class_map foreach(self::existingCustom('include/utils/class_map.php') as $file) { require $file; } // add composer classmap // 加载Elastica、OneLogin_Saml2等composer引用类,并与核心映射类数组合并 $class_map = array_merge($class_map, self::getComposerClassMap()); // 把$class_map保存到./include/class_map.php中 // Don't save to disk in development mode as the map will be ignored // and its content will not be incremental. if (!self::$devMode) { write_array_to_file("class_map", $class_map, sugar_cached(self::CLASS_CACHE_FILE)); } self::$classMap = $class_map; self::$classMapDirty = false; // 每次生成class_map.php后,此变量都为false,一旦为true,在项目结束后会重新生成class_map.php } // ./include/utils/autoloader.php // 先判断文件是否存在,再判断二次开发的文件是否存在 public static function existingCustom() { // 获取传过来的参数【文件/文件夹】 $files = func_get_args(); $out = array(); foreach($files as $file) { if(empty($file)) continue; if(is_array($file)) { $out += call_user_func_array(array("SugarAutoLoader", "existingCustom"), $file); continue; } if(self::fileExists($file)) { $out[] = $file; } // 判断二次开发的目录是否有此文件 if(substr($file, 0, 7) != 'custom/' && self::fileExists("custom/$file")) { $out[] = "custom/$file"; } } return $out; } // ./include/utils/autoloader.php // 看了代码后,这里只能是项目允许的文件,其他非法的文件就会返回失败 public static function fileExists($filename) { // 标准化文件路径,如d:\\a\\sugar\\c\\d\file.php ----> c/d/file.php // 这么做一方面为了和$exclude数组做比较 // 另一方面,set_include_path已经把工作目录设为了项目所在路径 // 如,在本文件引入文件,实际引入的是项目目录/文件路径 $filename = self::normalizeFilePath($filename); // 因为$exclude存放都是临时文件,因此要过滤这些,只要是这些目录的,只需判断文件是否存在 // See if this filename would have been skipped by the cache creator. This // addresses situations like module loader that call sugar_* file functions // that use the autoloader on files that are in cache, etc. $excluded = false; foreach (self::$exclude as $path) { if (strpos($filename, $path) === 0) { $excluded = true; break; } } if ($excluded) { // This is a filename that would have been skipped, so check the file // system for existence return file_exists($filename); } // 查询是否在缓存类数组里 // 第二次查询时就从此判断 if(isset(self::$memmap[$filename])) { return (bool)self::$memmap[$filename]; } // 在开发模式下./cache/file_map.php、./cache/class_map.php是不会有内容的 // 因此会直接判断文件是否存在 // Remember the file_exist calls in dev mode directly // without traversing the filemap as it's empty. This // will safe us multiple calls to file_exist within // the same page. if (self::$devMode) { self::$memmap[$filename] = file_exists($filename); return (bool)self::$memmap[$filename]; } // 从项目文件中查找,到了这一步没有就是没有了 // $filemap是通过扫描整个项目目录,收录了所允许的文件【self::$exts】 $parts = explode('/', $filename); $data = self::$filemap; foreach($parts as $part) { if(empty($part)) continue; // allow sequences of /s if(!isset($data[$part])) { self::$memmap[$filename] = false; return false; } $data = $data[$part]; } if($data || $data == array()) { self::$memmap[$filename] = true; return true; } self::$memmap[$filename] = false; return false; } // 载入composer引入类数组,并以此把路径[数组中的value]标准化 public static function getComposerClassMap() { $classMap = self::getIncludeReturn(self::$composerPaths['autoload_classmap']); return array_map(array('SugarAutoLoader', 'normalizeFilePath'), $classMap); } protected static function getIncludeReturn($file, $default = array()) { if (self::fileExists($file)) { $return = @include $file; } return (!isset($return) ? $default : $return); }
在loadFileMap下面,还有个self::loadClassMap()方法,此方法是生成class_map.php文件的,但是很鸡肋,因为此文件在上步已经生成了,除非手动删除了此文件,源码中的解释也模棱两口。
// Build class map (implicitly includes Composer's classmap) // 看了几遍代码后,仍是没有发现隐式包含的composer类【self::loadFileMap()已经都包含】 public static function loadClassMap() { $class_map = array(); // in development mode we start with a clean slate if (!self::$devMode) { @include sugar_cached(self::CLASS_CACHE_FILE); } if (empty($class_map)) { // oops, something happened to cache // try to rebuild self::buildClassCache(); } else { self::$classMap = $class_map; self::$classMapDirty = false; } }
最后来看看file_map.php和class_map.php存放的是什么。file_map.php存放的是整个项目内所允许的文件【self::$exts】,class_map.php存放着项目核心类及composer集成类以及在每次页面访问加载的类。
/** * Directories to exclude form mapping * 排除形式映射的目录 * 数组中的列举的目录都是缓存或是生成的目录,里面的文件不可控 * @var array */ public static $exclude = array( 'cache', 'custom/history', '.idea', 'custom/blowfish', 'custom/Extension', 'custom/backup', 'custom/modulebuilder', 'tests', 'examples', 'docs', 'vendor/log4php', 'upload', 'portal', 'vendor/HTMLPurifier', 'vendor/PHPMailer', 'vendor/reCaptcha', 'vendor/ytree', 'vendor/pclzip', 'vendor/nusoap', 'vendor/bin', ); /** * Extensions to include in mapping * @var array */ public static $exts = array( 'php', 'tpl', 'html', 'js', 'override', 'gif', 'png', 'jpg', 'tif', 'bmp', 'ico', 'css', 'xml', 'hbs', 'less', );