SugarCRM源码分析之loadFileMap


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


你可能感兴趣的:(SugarCRM源码分析之loadFileMap)