代码说

code is poetry

代码说    
碎碎念:自从你去到一个很远地方流浪,不知道现在已经变得怎么样。  换一换

深入探讨ZF2中命名空间下自动加载类文件的实现机制

作者:coderzheng 发布于:2015-3-7 9:44 Saturday 分类:php  阅读模式

我们在ZF2中使用命名空间来简化开发:

上面是一个典型的model程序中的一段代码,在ZF2教程的skeleton应用中,我们可以知道上面这种写法的含义是:
1) 给本页面定义一个命名空间,避免别的程序中的方法和本页面的方法产生冲突;
2) 主动将需要使用的函数所在的命名空间引用进来(一系列的use语句).
通过上面的步骤,在随后的程序中,如果代码引用了相关的类(PropertyListVO, Model, TableGateway...)的某个方法,php会自动加载相应的命名空间下的这个类,从而保证程序的正常运行。
但是自动加载的过程需要__autoload函数的配合,下面我们就来看看整个过程究竟是怎样实现的:
1) 首先我们来看namespace的作用:
编写一个php文件test_01.php,代码如下:
<?php
namespace NS\App;

function showValueOfFoo() {
     echo 'aaa';
}

//showValueOfFoo();
去掉注释之后,上面的php程序是可以正常运行的,类似得,我们再编写一个php文件test_02.php,代码如下:
<?php
namespace my\name2;

function showValueOfFoo() {
     echo 'bbb';
}

//showValueOfFoo();
第三个php文件test_03.php:
<?php
// 同时include两个具有相同函数名称的文件, 由于命名空间不同, 不会产生冲突
include 'test_01.php';
include 'test_02.php';

/*
// 试图直接调用某个文件中的函数, 会产生错误, 原因是由于引用的文件中定义的
// 方法都只能作用在相应的命名空间下, 因此这样直接调用相当于调用本页面中的全
// 局方法, 如果本页面并未定义这样的方法, 就会产生错误
showValueOfFoo();
*/

/*
// 直接引用命名空间调用方法 => OK
NS\App\showValueOfFoo();
my\name2\showValueOfFoo();
*/

/*
// 声明使用的命名空间, 然后调用方法 => OK
// 注意这里即使声明了使用的命名空间, 仍然不能省略命名空间的引用, 因为省略
// 了命名空间的引用时其含义是引用页面中的全局函数!!!
use NS\App;
NS\App\showValueOfFoo();
*/

// 使用use as 简化命名空间, 然后引用命名空间调用方法 => OK
// 这是最常见的用法.
use NS\App as NA;
NA\showValueOfFoo(); 

// 有些程序在前面先声明引用的命名空间, 然后在后面直接调用这个命名空间中的方法
// (并没有加命名空间的引用)也没有产生错误, 是因为程序事先使用__autoload方法处理过了.
你可以依次去掉test_03.php中的注释起止符号(/*和*?)来进行调试,每个部分都有相应的注释,注释部分解释了发生的一切。
最后两行注释告诉我们,ZF2中的实现机制就是配合__autoload方法来实现的。
继续编写test_04.php,来看一下__autoload函数的作用:
<?php
// 自动加载某个类
$obj = new MyClass1(); // classes/MyClass1.php is auto loaded
$obj = new MyClass2(); // classes/Myclass2.php is auto loaded

function __autoload($class_name) {
     require_once("classes/$class_name".".php");
}
/*
//类似的,自动载入命名空间类

use App\Lib1\MyClass as MC;

$obj = new MC(); // 自动加载classes/App/Lib1/MyClass.php
echo $obj->WhoAmI();

function __autoload($class) {
     $class = 'classes/' . str_replace('\\', '/', $class) . ".php";
     require_once($class);
}
*/
上面的程序,一种是在没有使用命名空间时,__autoload处理类加载的情况,一种是在使用了命名空间时__autoload处理类加载的情况。可以看到两种处理的方式还是有所不同的。对于第二种情况(使用命名空间),我们几乎已经接近真实的model类中程序结构了。接下来继续编写test_05.php,来看一个namespace、use和__autoload相结合的实例:
<?php
namespace Y\E;

use script\lib\test;
//require_once 'script/lib/test.php';

/*
自动加载script/lib/test.php, 注意在test.php中的命名空间必须和这里保持一致(
只有保持一致才能使用到同一个命名空间里的相应方法和变量!!!)
同时由于我们定义的自动加载文件的规则是已经固定在__autoload()函数中的,
因此命名空间不能随意命名, 一定是按照文件夹的层次来命名才行!!!
*/

$obj = new test();
//$obj = new \script\lib\test();
echo $obj->getWelcome();

function __autoload($className)
{
    $className = ltrim($className, '\\');
    $fileName  = '';
    $namespace = '';
    if ($lastNsPos = strrpos($className, '\\')) {
        $namespace = substr($className, 0, $lastNsPos);
        $className = substr($className, $lastNsPos + 1);
        $fileName  = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
    }
    $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
    require $fileName;
}
注释部分解释了命名空间的命名规则为什么要和文件夹的层次相契合。
亲爱的读者,到这里你一定以为我们已经模拟出了一个类似于本文开头图片中展示的model类程序,并且它还能正常运行。但是我很遗憾的告诉你,上面这个程序并不会正常运行:

奇怪的是,我们只要删除程序开头部分命名空间的定义,程序就能正常运行:

问题出在__autoload函数身上,这个函数只有在全局作用域下才能正常运行(或者说,如果放在命名空间下面,需要被动调用才能执行其中的代码),而我们却将它放在一个命名空间下面了!
最后,我们给出一个模拟真实场景的程序:
编写test_06.php:
<?php
namespace Y\E;

use script\lib\test;
$obj = new test();
echo $obj->getWelcome();
其实就是test_05.php中砍掉__autoload函数剩下的部分。
继续编写test_07.php:
<?php
require_once 'init_autoloader.php';
require_once 'test_06.php';

// look, it works well!
init_autoloader.php类似于ZF2中的AutoloaderFactory:

至此,test_07.php已经符合我们的所有要求。










over.










标签: ZendFramework2 php

你可以发表评论、引用到你的网站或博客,或通过RSS 2.0订阅这个博客的所有文章。
上一篇: wampserver中apache配置文件的一个坑  |  下一篇:linux下的git安装