Shyue

流逝的流年流淌着流传的流言~流浪的流氓继续着流亡......
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Zend Framework解析之Zend_Loader_Autoloader类结构及功能分析

Posted on 2012-04-15 23:25  Shyue  阅读(1482)  评论(0编辑  收藏  举报

简介

    自动加载(Autoloading)是一种在编码过程中用程序托管代替手动引入(require或include)所依赖的代码文件或类的机制。根据 » the PHP autoload manual, autoloader一经定义,那么在使用尚未定义(或未引入)的类或接口时,将会自动调用。

    使用自动加载机制不用担心类在项目中的具体位置。 通过设计良好的autoloaders不用关心当前class所关联的目标class的位置直接使用即可,自动加载器会自动查找并加载class文件。

    另外,因为自动加载机制的延迟加载并确保只匹配一次,所以特别是在正式部署前去掉多余的require_once()调用,将会获得很大的性能提升。

    ZF鼓励使用自动加载机制,并且提供了一些tools,以便既能够自动加载ZF的核心库代码有能够实现应用代码的自动加载。后面将会介绍这些工具及如何有效的使用他们。 

1、设计和目标 

1)、命名规范约定  

      要理解ZF的自动加载机制首先需要理解ZF中类名和类文件之间的关系。ZF借用了» PEAR的类名和文件系统一一对应的理念。简言之,就是用目录分隔符"/"(windows是"\\")来替换类名中的下划线"_"的方式解决文件路径问题,然后添加后缀".php"组成类文件名以映射到文件系统中的具体文件。例如,类"Foo_Bar_Baz"将会被对应到文件系统中的"Foo/Bar/Baz.php"文件。同时假设类可以在PHP'sinclude_path 的相对路径中查找和引入。

      另外,根据PEAR和» PHP project 推 荐使用公司名称或项目名称为代码设置通用前缀。这样一来你写的所有类都会共用统一个类前缀;例如,ZF的所有代码都用"Zend_"前缀。这种命名规范可 以有效的避免命名冲突。在ZF中通常将此称作为"名称空间"前缀;但注意不要喝PHP内建的 命名空间 混淆。PHP从5.3开始引入内置的 namespace 概念。

      ZF内部遵循这些简单的规则,同时也鼓励开发者使用同样的规则。 

2)、自动加载机制的设计约定

     ZF的自动加载机制是由Zend_Loader_Autoloader 提供的,有下面几个目的和设计理念:

a、提供命名空间匹配。

如果类的namespace前缀尚未注册,立即返回FALSE。它允许更多匹配,同时作为其他加载器的后备。

b、允许autoloader作为后备自动加载器。 

在广泛发布或使用尚未确定的namespace前缀集的情况下,自动加载应该仍是可配置的,这样他将尝试匹配任何命名空间前缀。但不推荐这样做,这会导致不必要的查找。

c、允许触发(开关)错误抑制。

错误抑制通常不是个好主意,它会掩盖很多实际的问题。所以,默认是关闭的。当然,如果开发者坚持认为应该开启,也可以开启它。

d、 允许为自动加载指定之定义的回调方法。

有些开发者不想用Zend_Loader::loadClass() 作为自动加载,但仍然想使用ZF的加载机制。Zend_Loader_Autoloader 允许为自动加载机制指定另外的回调方法。

e、允许操作SPL autoload 回调链。 

这样做的目的是允许在ZF的autoloader之前或之后指定注册另外的autoloaders。例如,为和文件系统没有一一映射的类注册的资源加载器。

 

现在对ZF自动加载解决方案及其设计目标有了一定了解,接下来让我们介绍如何使用Zend_Loader_Autoloader

2、结构及功能解析:

Zend_Loader_Autoloader实现了单例模式,使得它可以全局访问。这就为在代码的任意位置注册另外的自动加载器提供了可能性。我们只需要简单的require类文件然使用它的静态方法getInstance(),即可取得一个示例: 

require_once 'Zend/Loader/Autoloader.php';

$autoloader = Zend_Loader_Autoloader::getInstance();

第一次取得autoloader实例时,它便使用spl_autoload注册了自己。这是其在受保护的构造函数中实现的,同时还设置了内部自动加载回调:

    protected function __construct()

    {

        spl_autoload_register(array(__CLASS__, 'autoload'));

        $this->_internalAutoloader = array($this, '_autoload');

}

 

3、注册自定义名称空间:

默认,该自动加载器被配置为匹配“Zend_ 和“ZendX_”命名空间($_namespace属性将这两个名称空间设为默认),如果你的代码库使用了自己的namespace你可以使用 registerNamespace()方法注册该名称空间,注册成功后autoloader同时也匹配该名称空间。例如,如果你的代码库使用“My_ 做前缀,那么你可以这样注册自己的名称空间:

$autoloader->registerNamespace('My_');

如此My_MyClass类将使用该autoload(“My_MyClass”)加载。同样你也可以注册任意其他回调方法作为自动加载器方法,可为可选参 数指定namespacenamespace数组。Zend_Loader_Autoloader 将会首先尝试匹配这些命名空间加载器,若无对应的匹配将使用内部自动加载机制。例如,你可能想在你的ZF应用中使用一个或多个eZcomponents 组件。要使用他们的自动加载能力,可以用 pushAutoloader()将它压到自动加载堆栈中:

$autoloader->pushAutoloader(array('ezcBase', 'autoload'), 'ezc');

这会告诉autoloader为以“ezc”开头的类使用eZcomponents'ezcBase'类的'autoload'方法作自动加载器。还可以使用unshiftAutoloader()方法将自动加载回调方法压到加载器堆栈的栈顶位置。

 

4、抑制错误报告:

默认,当使用内部加载器时Zend_Loader_Autoloader不会抑制错误,这在大多数时候会表现得很好,然而可能有些特殊情况要抑制错误,那么可以使用suppressNotFoundWarnings()方法设置:

$autoloader->suppressNotFoundWarnings(true);

Zend_Loader_Autoloader类使用下面的属性指明了默认的自动加载器:$_defaultAutoloader = array('Zend_Loader', 'loadClass');那就是Zend_Loader::loadClass(),同时又在构造函数中将其设置为内部加载器。

 

5、设为后备加载器,但不推荐使用:

最后,你可能有时候想让autoloader 能够加载任意namespace下的类。例如,PEAR库没有使用统一的namespace,当使用多个PEAR组件时要给每一个组件指定单独的 namespace是很困难的。这种情况下可以使用setFallbackAutoloader()方法强制使autoloader可以匹配所有 namespace

$autoloader->setFallbackAutoloader(true);

 

当实例化一个类A时,会自动调用'autoload'方法,该方法首先读取针对类A的命名空间注册的加载器,即通过pushAutoloader() unshiftAutoloader()注册的加载器。然后读取ZF的内部加载器和非命名空间的加载器,若目标类A无名称空间且该autoload被设置为了后备加载器那么将会使用内部加载器进行加载。

 

6、不同研发阶段ZendFramework版本选择问题的解决方案:

通常,我们使用ZF时都是带有autoloader实例的。然而,当开发一个项目时跟踪某特定版本通常是非常有用的,例如,major minor 分支,或者最新版本。ZF 1.10版本开始Zend_Loader_Autoloader提供了版本管理的新特性。

想象下面的场景:

1.   开发过程中你可能想跟踪最新版本的ZF,以便确定更新版本后应用可以正常工作。

2.   QA阶段你需要一个稍微稳定的版本,所以你可能想用最新版本的上一版本。

3.   最终作为产品发布时可能使用特定版本或者添加新版本以确定没有错误或bug出现。

Zend_Loader_Autoloader允许通过 setZfPath()满足这些需求。这个方法需要2个参数,一个是ZF版本的路径,另一个是要使用的ZF的版本。调用该方法会将指定的ZF版本的路径添加到include_path前端。指定的Path目录应该有下面的树状结构:

    ZendFramework/

    |-- 1.11.11/

    |   |-- library/

|-- ZendFramework-1.11.10-minimal/

|   |-- library/

    |-- ZendFramework-1.10.0

    |   |-- library/

注意每一个子目录必须包含library目录,这个目录保存了实际的ZF库的代码。这个特定的子目录名可能是一个版本号,或者是一个标准的ZF发行版本tar/zip文件解压出来的内容。现在让我们看几个例子:

第一个例子,在研发过程中我们想追踪最新的版本,那么我们可以指定版本“latest”:

$autoloader->setZfPath($path, 'latest');

在上面的例子中这会映射到ZendFramework/1.11.11/library/目录,可以用getZfPath()返回的值进行比较验证。

第二种QA的情况,如果你想确定使用1.11.10这次要版本,你可以这样指定:

$autoloader->setZfPath($path, '1.11.10');

在这个例子中将会映射到ZendFramework/1.11.10/library/这个目录中。

最后在生产环境中我们使用特定的版本1.10.0版本,同样可以使用这个方法指定。

 

同样还可以通过在配置文件中配置指定各个阶段使用的ZF版本:

     [production]

      autoloaderZfPath = "path/to/ZendFramework"

      autoloaderZfVersion = "1.11.11"

     [qa]

     autoloaderZfVersion = "1.11.10" 

     [development]

     autoloaderZfVersion = "1.10.0"