Zend Framework 代码分析——Zend_Loader

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的是可圈可点的。其他部分的代码一定更加精彩。

Join the Conversation

1 Comment

Leave a comment

Your email address will not be published. Required fields are marked *