Zend Framework1.9 启动过程跟踪解析 ZF升级太快,已经很久没有阅读新的源码了,今天遇到了点问题,决定冷静下来跟踪源码全面分析一下zf的启动过程。貌似在启动过程中,1.9与1.8并没有什么变化,但我阅读的类库源码是1.9的,所以标题也就写了1.9,倒没有什么哗众取宠的意思。 一、.htaccess
SetEnv APPLICATION_ENV testing
RewriteEngine On
RewriteCond
%{
REQUEST_FILENAME
} -
s
[OR]
RewriteCond
%{
REQUEST_FILENAME
} -
l
[OR]
RewriteCond
%{
REQUEST_FILENAME
} -
d RewriteRule
^.*$ - [
NC
,
L
]
RewriteRule
^.*$
index
.
php
[
NC
,
L
]
这段代码由ZEND STUDIO 7.0构建项目时自动生成。它的作用有两个,一是指定当前程序运行环境为testing,二是对客户端输入的url地址进行rewrite转址。如果输入的文件地址存在,就执行该文件,如果输入的地址不存在,就自动转到index.php上。 二、index.php
// Define path to application directory
defined('APPLICATION_PATH')
|| define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));
// Define application environment
defined('APPLICATION_ENV')
|| define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production'));
// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
realpath(APPLICATION_PATH . '/../library'),
get_include_path(),
)));
/** Zend_Application */
require_once 'Zend/Application.php';
// Create application, bootstrap, and run
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);
$application->bootstrap()
->run();
index.php定义了程序根文件夹APPLICATION_PATH、程序运行环境APPLICATION_ENV,并把ZEND FRAMEWORD库文件位置加入到include_path中,以便调用。最后,实例化了Zend_Application类,并执行了它的bootstrap()和run方法。 三、Zend\Application.php Application.php是Zend类库的组成部分,位于zend文件夹下。源码较长,就不复制过来了。 实例化Zend_Application时,只是根据输入的ENV和option参数,确定系统的环境变量。如上文所述,通常我们是这样实例化的:
$application
= new
Zend_Application
(
APPLICATION_ENV
,
APPLICATION_PATH
.
'/configs/application.ini'
);
用这种方法,我们依据ENV来取application.ini中设定的参数。 以下是一个简短的INI设置:
[
production
]
phpSettings
.
display_startup_errors
=
0 phpSettings
.
display_errors
=
0
;
includePaths
.
library
=
APPLICATION_PATH
"/../library"
bootstrap
.
path
=
APPLICATION_PATH
"/Bootstrap.php"
bootstrap
.class =
"Bootstrap"
resources
.
frontController
.
controllerDirectory
=
APPLICATION_PATH
"/controllers"
[
staging
:
production
] [
testing
:
production
]
phpSettings
.
display_startup_errors
=
1 phpSettings
.
display_errors
=
1
[
development
:
production
]
phpSettings
.
display_startup_errors
=
1 phpSettings
.
display_errors
=
1
实例化zend_application时,将把production段载入$options数组,由于我们设定的APPLCATION_ENV是testing,所以接下来会载入testing段。而straging和development段会被忽略。日后程序正式上架运行时,我们只要在服务器上把环境配置为staging,然后在staging段中配置本地参数如数据库等,就可以简单地区分运行环境,避免维护困扰。比如本地和服务器的数据库一般不会是同一个,就可以分别在staging和testing中设置各自的数据库配置信息。 Zend_Application中的第二个参数options也可以是一个符合Zend_Config结构的对象,也就是说,可以先用Zend_Config类获取config数据,然后传递过来。这个功能我目前没有用到,也想不出什么情况下适合采用这种方式调用,哪位达人想到的,还请交流一下。 实例化后的数组,可以用getOptions()获取,也可以用getOption($key)获取其中的特定参数,还可以用mergeOptions()进行合并,本文结构是以程序流程组织的,目前还没有用到这些功能,就先不阐述了。 在以下代码中,$application->bootstrap()实例化了Zend_Application_Bootstrap_Bootstrap,$application->bootstrap()调用了Zend_Application_Bootstrap_Bootstrap的run方法:
public function getBootstrap()
{
if (null === $this->_bootstrap) {
$this->_bootstrap = new Zend_Application_Bootstrap_Bootstrap($this);
}
return $this->_bootstrap;
}
public function bootstrap($resource = null)
{
$this->getBootstrap()->bootstrap($resource);
return $this;
}
public function run()
{
$this->getBootstrap()->run();
}
注意其中的这一行代码:
$this
->
_bootstrap
= new
Zend_Application_Bootstrap_Bootstrap
(
$this
);
application把自己的实例作为参数,传递给了bootstrap.也就是说bootstrap将不但可以得到具体参数,还可以继续执行application中包含的方法。
四、Zend\Application\Bootstrap\Bootstrap.php Bootstrap.php基于Zend_Application_Bootstrap_BootstrapAbstract派生。它本身只改动了类的两个方法:
public function __construct($application)
{
parent::__construct($application);
if (!$this->hasPluginResource('FrontController')) {
$this->registerPluginResource('FrontController');
}
}
这段代码在实例化时检查有没有前端控制器,如果没有就注册前端控制器。调用了父类中的hasPluginResource()和registerPluginResource()方法。
public function run()
{
$front = $this->getResource('FrontController');
$default = $front->getDefaultModule();
if (null === $front->getControllerDirectory($default)) {
throw new Zend_Application_Bootstrap_Exception(
'No default controller directory registered with front controller'
);
}
$front->setParam('bootstrap', $this);
$front->dispatch();
}
这段代码调用程序默认模块执行代码显示页面。程序执行到此,运行宣告结束。但该段代码中调用的内容不少,如父类中的getResource()、getDefaultModule()、getControllerDirectory()方法, $front->setParam和$front->dispatch方法,明显来源于前端控制器zend_controller。所以,接下来还有大量代码需要跟踪。 五、Zend\Application\Bootstrap\Zend_Application_Bootstrap_BootstrapAbstract 实例化本类的派生类时,__construct代码如下:
public function __construct($application)
{
$this->setApplication($application);
$options = $application->getOptions();
$this->setOptions($options);
}
这段代码通过setApplication()方法处理$application类,将其定义为一个内部变量$this->_application,然后通过setOptions()方法处理$设置的options选项。 setApplication()方法非常简单,就不提了。setOptions()方法相对复杂了一些:
public function setOptions(array $options)
{
$this->_options = $this->mergeOptions($this->_options, $options);//把所有options选项合并成一个数组
$options = array_change_key_case($options, CASE_LOWER);
$this->_optionKeys = array_merge($this->_optionKeys, array_keys($options));
$methods = get_class_methods($this);
foreach ($methods as $key => $method) {
$methods[$key] = strtolower($method);
}
if (array_key_exists('pluginpaths', $options)) {
$pluginLoader = $this->getPluginLoader();
foreach ($options['pluginpaths'] as $prefix => $path) {
$pluginLoader->addPrefixPath($prefix, $path);
}
unset($options['pluginpaths']);
}
foreach ($options as $key => $value) {
$method = 'set' . strtolower($key);
if (in_array($method, $methods)) {
$this->$method($value);
} elseif ('resources' == $key) {
foreach ($value as $resource => $resourceOptions) {
$this->registerPluginResource($resource, $resourceOptions);
}
}
}
return $this;
}
这段代码中,先把所有options选项合并成一个数组,然后把所有key值转化为小写,获取本类中的所有方法并同样把它们的key值转化为小写。 接着,判断是否$options中是否配置了插件,如果有,就实例化Zend_Loader_PluginLoader($options),并加载对应插件路径。 然后,寻找所有资源是否在本类中有对应的set方法,如果有就执行。 最后,通过registerPluginResource()方法注册options中设定的所有resources资源。 然后,我们回到index.ini中的最后一行:$application->run(),我们已经知道,它调用的是Bootstrap.php中的run方法:
public function run()
{
$front = $this->getResource('FrontController');
$default = $front->getDefaultModule();
if (null === $front->getControllerDirectory($default)) {
throw new Zend_Application_Bootstrap_Exception(
'No default controller directory registered with front controller'
);
}
$front->setParam('bootstrap', $this);
$front->dispatch();
}
这其中的getResource('FrontController')方法,事实上是调用zend_registry类来获取预存的FrontController前端控制器。
$default = $front->getDefaultModule();获取默认的controller
$front->setParam('bootstrap', $this);把bootstrap作为参数传递给前端控制器
$front->dispatch();执行和输出
|