在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',
);