为了提高基于ThinkPHP开发应用的性能,ThinkPHP正式版本中引入了项目编译机制。所谓的项目编译机制指当应用第一次被运行时,系统会在运行时目录下生成两个编译缓存文件:~runtime.php和~app.php,前者为核心编译缓存文件,后者为项目编译缓存文件。这两个文件打包了执行应用所需要的一些脚本文件内容,这样做的目的,是为了省去每次执行应用时重复的I/O开销,提高应用性能。
核心编译缓存文件中包含的文件有三种不同的情况:如果用户在自身的项目配置目录中配置了core.php文件,那么~runtime.php中就包含core.php文件中返回的文件列表下的文件;如果用户设置了项目特定的运行模式,那么~runtime.php中就包含该模式下所定义的核心文件;除此之外,~runtime.php中包含的就是ThinkPHP默认的核心文件,这些文件由系统目录下公共目录下的core.php决定。
// 读取核心编译文件列表 if(is_file(CONFIG_PATH.'core.php')) { // 加载项目自定义的核心编译文件列表 $list = include CONFIG_PATH.'core.php'; // 可以自定义核心编译文件列表(s) }elseif(defined('THINK_MODE')) { // 根据设置的运行模式加载不同的核心编译文件 $list = include THINK_PATH.'/Mode/'.strtolower(THINK_MODE).'.php'; }else{ // 默认核心 $list = include THINK_PATH.'/Common/core.php'; // * important *(s) }
通常情况下,使用框架默认的核心文件就能满足大部分应用要求。 核心文件返回如下8个文件:系统函数库(functions.php),系统基类(Think.class.php),异常处理类(ThinkException.class.php),日志处理类(Log.class.php),应用程序类(App.class.php),控制器类(Action.class.php),视图类(View.class.php)和别名类(alias.php)。如果应用有特别的核心文件需要编译缓存,也可以将文件加入到数组中。
// 系统默认的核心列表文件 return array( THINK_PATH.'/Common/functions.php', // 系统函数库 THINK_PATH.'/Lib/Think/Core/Think.class.php', // 系统核心类库基类 THINK_PATH.'/Lib/Think/Exception/ThinkException.class.php', // 异常处理类 THINK_PATH.'/Lib/Think/Core/Log.class.php', // 日志处理类 THINK_PATH.'/Lib/Think/Core/App.class.php', // 应用程序类 THINK_PATH.'/Lib/Think/Core/Action.class.php', // 控制器类 //THINK_PATH.'/Lib/Think/Core/Model.class.php', // 模型类 THINK_PATH.'/Lib/Think/Core/View.class.php', // 视图类 THINK_PATH.'/Common/alias.php', // 加载别名 );
同时,如果环境中PHP版本低于5.2.0,~runtime.php还会包含系统提供的兼容函数库compat.php, 更大的兼容各种不同的环境。
为了更有效率的包含这些核心文件,ThinkPHP采取了foreach遍历加载文件的方法。至此,已经可以运行一个应用了。但是,这样存在一个问题,就是每次执行应用的时候,系统都会重复上面的操作,这将造成很大的I/O开销,当访问量达到一定数量后,服务器将会不堪重负。为了解决这个问题,ThinkPHP提供了一种解决方案,就是使用编译缓存机制。具体实现是将核心文件内容编译到同一个文件中,这样每次执行应用时,只需加载这个编译缓存文件即可,这样就极大地减少了I/O开销,增强了应用程序的性能。
ThinkPHP项目编译机制主要借助系统函数compile()完成,compile()函数位于系统函数库中,其具体内容如下:
function compile($filename, $runtime=false) { $content = file_get_contents($filename); if (true === $runtime) // 替换预编译指令(ALLINONE模式) $content = preg_replace('/\/\/\[RUNTIME\](.*?)\/\/\[\/RUNTIME\]/s', '', $content); $content = substr(trim($content), 5); if ('?>' == substr($content, -2)) $content = substr($content, 0, -2); return $content; }
可以看到,函数通过将文件内容读入到一个字符串,然后对字符串进行了一些截取处理,去掉了PHP的开始和结束标记。最后返回包含文件内容的字符串。
有了这个compile()函数,余下就是调用这个函数处理核心文件并将这些处理后得到的字符串连接的过程。考虑到开发者在使用ThinkPHP进行开发会经历开发调试与部署两种不同环境,ThinkPHP为开发者提供了两种不同的编译缓存模式。一种是仅仅将核心文件内容简单的编译在了一个文件中,这种模式方便开发者在开发过程中快速的进行错误定位。另一种模式则不仅仅是对文件内容的简单叠加,还去除了其中的注释和空白进行,这种模式适合于部署环境,保证服务器更快的响应。
if(!defined('NO_CACHE_RUNTIME')) { // 生成核心编译缓存文件。 $compile = defined('RUNTIME_ALLINONE'); // return 'true' or 'false'(s) $content = compile(THINK_PATH.'/Common/defines.php',$compile); $content .= compile(defined('PATH_DEFINE_FILE')? PATH_DEFINE_FILE : THINK_PATH.'/Common/paths.php',$compile); foreach ($list as $file){ $content .= compile($file,$compile); } $runtime = defined('THINK_MODE')?'~'.strtolower(THINK_MODE).'_runtime.php':'~runtime.php'; if(defined('STRIP_RUNTIME_SPACE') && STRIP_RUNTIME_SPACE == false ) { file_put_contents(RUNTIME_PATH.$runtime,'<?php'.$content); }else{ file_put_contents(RUNTIME_PATH.$runtime,strip_whitespace('<?php'.$content)); } unset($content); }
这样就生成了核心编译缓存文件~runtime.php。
项目编译缓存文件~app.php则是通过调用App类下的init方法生成的。在第一次运行应用时,应用程序初始化,系统会预编译项目。项目编译缓存文件中包含:惯例配置文件,项目配置文件,公共函数文件以及自定义的项目编译文件列表文件。
(完)