Zend_Loader 应该是 Zend Framework 中相对简单的类。所有方法都为静态方法,用于加载(loading)类或文件(loadClass、loadFile、autoload、registerAutoload 方法),以及加载类或文件时进行的必要判断(isReadable 方法)。
Zend_Loader 从 3834 版本被加入 Zend Framework 并没有太多的改动。除了 4259 版本添加了 registerAutoload 方法。
public static function loadClass($class, $dirs = null)
方法是高效的加载类的方法。类的文件命名必须是 {$className}.php。当第二个参数 $dirs 为文件夹名或元素是文件夹名的数组时,将从这些文件夹中找到第一个匹配的文件进行加载。而如果 $dirs 为 null,则将类名的下划线作为分隔,划分为文件夹。例如 Zend_Foo_Bar,将会加载 Zend/Foo/Bar.php这个文件。若无法在上述所有地方找到可用的文件加载类,则将对 include_path进行搜索。
if (class_exists($class, false) || interface_exists($class, false)) {
return;
}
首先判断是否已经加载,这里使用了 interface_exists,在 php 中 interface 可以被认为是一种特殊的类。
if ((null !== $dirs) && !is_string($dirs) && !is_array($dirs)) {
require_once 'Zend/Exception.php';
throw new Zend_Exception('Directory argument must be a string or an array');
}
if (null === $dirs) {
$dirs = array();
}
if (is_string($dirs)) {
$dirs = (array) $dirs;
}
若类和接口没有被加载,则对 $dirs 进行验证,判断是否为合法的类型:null、字符串、数组,否则抛出异常。而最终 $dirs 都将转换为数组处理。
下面比较重要:
$path = str_replace('_', DIRECTORY_SEPARATOR, $class);// 这里用路径分隔符号替换了下划线,DIRECTORY_SEPARATOR 是 php 自带的常量。
if ($path != $class) {// 若得到的路径就是类名则直接使用这个类名作为要加载的文件名,否则获取类的路径。
$dirPath = dirname($path);// 取得上面替换后由类名产生的路径。
if (0 == count($dirs)) {// 若 $dirs 为空,则将类名产生的路径放入这个数组。
$dirs = array($dirPath);
} else {// 对 $dirs 进行解析。
foreach ($dirs as $key => $dir) {
if ($dir == '.') {// 为当前目录,则替换为类名产生的路径。
$dirs[$key] = $dirPath;
} else {
$dir = rtrim($dir, '\\/');// 将路径末尾的 DIRECTORY_SEPARATOR 去除。
$dirs[$key] = $dir . DIRECTORY_SEPARATOR . $dirPath;// 将指定路径和类名替换的路径合并。
}
}
}
$file = basename($path) . '.php';
} else {
$file = $class . '.php';
}
这样就获得了要加载的类的文件名。
self::loadFile($file, $dirs, true);
if (!class_exists($class, false) && !interface_exists($class, false)) {
require_once ‘Zend/Exception.php’;
throw new Zend_Exception(“File \”$file\” was loaded but class \”$class\” was not found in the file”);
}
通过 Zend_Loader::loadFile()方法加载类所在的文件。这里很关键的是如果上面得到的文件中没有要加载的类,也是需要抛出异常的。这里是为了加载类,而不是仅仅加载匹配的文件。从这点可以看出 Zend Framework 在设计上是非常健壮的。
public static function loadFile($filename, $dirs = null, $once = false)
在 $dirs 路径中搜索 $filename 所指定的文件并加载。$once 参数是用于判断使用 include还是 include_once加载。
if (preg_match('/[^a-z0-9\-_.]/i', $filename)) {
require_once 'Zend/Exception.php';
throw new Zend_Exception('Security check: Illegal character in filename');
}
从这段还是可以看出 Zend Framework在设计上的健壮性。检查文件名是否有非法字符,如果有则抛出异常。这样可以避免由于不正确的编码方式造成被加载非法文件的情况。
if (empty($dirs)) {// 这里我认为 is_null() 会更加健壮一些。
$dirs = array();
} elseif (is_string($dirs)) {
$dirs = explode(PATH_SEPARATOR, $dirs);
}
foreach ($dirs as $dir) {
$filespec = rtrim($dir, '\\/') . DIRECTORY_SEPARATOR . $filename;
if (self::isReadable($filespec)) { // 如果文件可读,则加载。
return self::_includeFile($filespec, $once);// 这是一个 protected 方法,代码见后。
}
}
整理 $dirs 为数组。并循环合并路径与文件名,判断是否可加载。若可加载则加载并返回。
if (self::isReadable($filename)) {
return self::_includeFile($filename, $once);
}
这段看似跟上面的代码重复,实际上这是直接在 include_path 中搜索要加载的文件。
require_once 'Zend/Exception.php';
throw new Zend_Exception("File \"$filename\" was not found");
如果代码能执行到这里,则说明文件不存在。非常高效的方法,不需要去判断文件是不是存在。可以学习一下。
protected static function _includeFile($filespec, $once = false)
{
if ($once) {
return include_once $filespec;
} else {
return include $filespec ;
}
}
方法本身非常简单,根据注释,没有在前面添加@符号的原因是会很难调试。的确如此,不是么?
public static function isReadable($filename)
{
if (@is_readable($filename)) {
return true;
}
// 若程序能执行到这里,说明文件不可读,那么下面继续在 include path 中搜索该文件。
$path = get_include_path();
$dirs = explode(PATH_SEPARATOR, $path);
foreach ($dirs as $dir) {
if (‘.’ == $dir) {
continue;
}
if (@is_readable($dir . DIRECTORY_SEPARATOR . $filename)) {
return true;
}
}
return false;
}
php 本身是有 is_readable() 函数的,那么这个方法看起来有些不必要。实际上这个方法允许对 include_path中的文件进行判断,这是 is_readable 无法完成的。很方便!
public static function autoload($class)
{
try {
self::loadClass($class);
return $class;
} catch (Exception $e) {
return false;
}
}
自动加载,很多人认为这个方法没什么用。因为直接调用 Zend_Loader::autoload() 的效果跟 Zend_Loader::loadClass() 完全一样。所以包括我之前的做法都是使用function __autoload() 这个魔法函数,直接加载 Zend_Loader::loadClass()。后来阅读了源代码后才发现这个方法的真正用法:
spl_autoload_register(array('Zend_Loader', 'autoload'));
public static function registerAutoload($class = 'Zend_Loader')
{
if (!function_exists('spl_autoload_register')) {
require_once 'Zend/Exception.php';
throw new Zend_Exception('spl_autoload does not exist in this PHP installation');
}
self::loadClass($class);
$methods = get_class_methods($class);
if (!in_array(‘autoload’, (array) $methods)) {
require_once ‘Zend/Exception.php’;
throw new Zend_Exception(“The class \”$class\” does not have an autoload() method”);
}
spl_autoload_register(array($class, ‘autoload’));
}
也许是 Zend Framework 的开发者认为直接使用 spl_autoload_register 不够优美。于是封装了这个方法来使用。很好!很方便!原来大家好才是真的好!
补充说明:之前我看到很多人在质疑 Zend Framework 的可用度和健壮度。在简单分析了 Zend_Loader 这个类的代码之后,确实可以发现 Zend Framework 在代码的设计上非常精致。虽然这个类完全由静态方法组成,并没有很多非常高级的用法。但是但从代码编写的状况来看,我相信整个ZendFramework的是可圈可点的。其他部分的代码一定更加精彩。
Leave a Reply