1. 概述
php的autoload机制就不再累述了,autoload机制确实能给开发带来便利,但是实现不好的话会很影响性能,比如:
function __autoload($class_name) {
include_once $class_name . '.php';
}
这是最简单的autoload实现函数,只要求类名和文件名保持一致,这实现起来很简单,但性能却不是很好,因为每次触发autoload函数会去遍历 include_path指定的文件夹查找匹配的文件。优化方案很简单,将类和文件地址的映射关系事先保存在一个文件里,autoload只需要在这个文件里根据类名查找到相应的文件地址,然后include文件,这样不用反复的去遍历文件夹,性能能得到一定的提升。下面来看看怎样实现。
2. 生成映射文件
在项目运行起来之前需要生成映射文件,代码如下:
class AssemblyBuilder
{
private static $_skipFolders = array('.','..','.git');
private static $_classes = array();
private static $_autoload_clspath = "_autoload_clspath";
public static function run($path)
{
$contents = "";
self::scanFolder($path);
if(!empty(self::$_classes))
{
foreach(self::$_classes as $k => $v)
{
$contents = $contents."$k,$v\n";
}
file_put_contents(self::$_autoload_clspath,$contents);
}
}
public static function getAutoloadClspath()
{
return self::$_autoload_clspath;
}
public static function scanFolder($path)
{
$dirs = scandir($path);
if(is_array($dirs) && !empty($dirs))
{
foreach($dirs as $dir)
{
$fullPath = $path.'/'.$dir;
if(is_dir($fullPath))
{
if(self::isSkipFolder($dir))
continue;
self::scanFolder($fullPath);
}
elseif(is_file($fullPath))
{
self::scanFile($fullPath);
}
}
}
}
public static function scanFile($file)
{
$lastPointPos = strrpos($file, ".");
if($lastPointPos)
{
$suffix = substr($file, $lastPointPos+1, strlen($file) - $lastPointPos);
if($suffix == 'php')
{
self::extractClassName($file);
}
}
}
public static function isSkipFolder($dir)
{
return in_array($dir,self::$_skipFolders);
}
public static function extractClassName($file)
{
$content = file($file);
foreach($content as $line)
{
if(preg_match('/^\s*(abstract\s*)?(interface|class)\s*(\S+)/',$line,$match))
{
$className = $match[3];
self::$_classes[$className] = $file;
}
}
}
}
$path = getcwd();
AssemblyBuilder::run($path);
装配器中的三个私有变量,skipFolders数组配置了在遍历文件夹时需要跳过的文件夹,classes数组保持了类名和文件的映射关系,autoload_clspath配置了映射文件名。
每次修改了类名或者添加了新类都需要重新运行上面的脚本以更新映射文件。
3. autoload实现
有了映射文件就可以根据类名来查找相应的文件,然后require文件,实现代码如下:
function __autoload($name)
{
$lines = file(dirname(__FILE__).'/'.AssemblyBuilder::getAutoloadClspath());
if(is_array($lines)&&!empty($lines))
foreach($lines as $line)
{
$arr = explode(',',$line);
$path = trim($arr[1]);
if($arr[0] == $name)
{
require_once($path);
break;
}
}
}
还可以优化,加一个全局缓存,这样第一次加载一个类时,查找到对应的文件,将文件名放入缓存,这样下次加载同一个类时可以从缓存里读取文件名,不同再遍历映射文件了。