注:非教程,只是研究过程一些心得记录。大部分抄,加之整理。
一 目录文件
|-framework 框架核心库
|--base 底层类库文件夹,包含:
CComponent(组件类,该文件包含了基于组件和事件驱动编程的基础类,从版本1.1.0开始,一个行为的属性(或者它的公共
成员变量或它通过getter和/或setter方法【类似.net中的property(属性)概念】定义的属性)可以通过组件的访问来调用)。
↑ CModel extends CComponent,模型类,为所有的数据模型提供的基类
↑ CBehavior extends CComponent,行为类,主要负责声明事件和相应事件处理程序的方法、将对象的行为附加到组件等等
↑ CModule extends CComponent,是模块和应用程序的基类,主要负责应用组件和子模块)等等
↑ CApplicationextends CModule,应用类,负责全局的用户请求处理,它管理的应用组件集,提供特定功能给整个应用程序
|--caching 所有缓存方法,其中包含了Memcache缓存,APC缓存,数据缓存,CDummyCache虚拟缓存,
CEAcceleratorCache缓存等等各种缓存方法
|--cli YII项目生成脚本
|--collections 用php语言构造传统OO语言的数据存储单元。如:队列,栈,哈希表等等
|--console YII控制台
|--db 数据库操作类
|--gii YII 代码生成器(脚手架),能生成包括模型,控制器,视图等代码
|--i18n YII 多语言,提供各种语言的本地化数据,信息、文件的翻译服务、本地化日期和时间格式,数字等
|--logging 日志组件,YII提供了灵活和可扩展的日志记录功能。消息记录可分为根据日志级别和信息类别。
应用层次和类别过滤器,可进一步选择的消息路由到不同的目的地,例如文件,电子邮件,浏览器窗口,等
|--messages 提示信息的多语言
|--test YII提供的测试,包括单元测试和功能测试
|--utils 提供了常用的格式化方法
|--validators 提供了各种验证方法
|--vendors 这个文件夹包括第三方由Yii框架使用的资料库
|--views 提供了YII错误、日志、配置文件的多语言视图
|--web YII所有开发应用的方法
|---actions 控制器操作类
|---auth 权限认识类,包括身份认证,访问控制过滤,基本角色的访问控制等
|---filters 过滤器,可被配置在控制器动作执行之前或之后执行。例如, 访问控制过滤器将被执行以确保
在执行请求的动作之前用户已通过身份验证;性能过滤器可用于测量控制器执行所用的时间
|---form 表单生成方法
|---helpers 视图助手,包含GOOGLE AJAX API,创建HTML,JSON,JAVASCRIPT相关功能
|---js JS库
|---renderers 视图渲染组件
|---services 封装SoapServer并提供了一个基于WSDL的Web服务
|---widgets 部件
|---CArrayDataProvider.php 可以配置的排序和分页属性自定义排序和分页的行为
|---CActiveDataProvider.php ActiveRecord方法类
|---CController.php 控制器方法,主要负责协调模型和视图之间的交互
|---CPagination.php 分页类
|---CUploadedFile.php 上传文件类
|---CUrlManager.php URL管理
|---CWebModule.php 应用模块管理,应用程序模块可被视为一个独立的子应用等等方法
|--.htaccess 重定向文件
|--yii.php 引导文件
|--YiiBase.php YiiBase类最主要的功能是注册了自动加载类方法,加载框架要用到所有接口。
|--yiic Yii LINUX 命令行脚本
|--yiic.bat YII WINDOW 命令行脚本
|--yiilite.php 它是一些常用到的 Yii 类文件的合并文件。在文件中,注释和跟踪语句都被去除。
因此,使用 yiilite.php 将减少被引用的文件数量并避免执行跟踪语句
1. 启动
网站的唯一入口程序 index.php :
$yii=dirname(__FILE__).'/../framework/yii.php';
$config=dirname(__FILE__).'/protected/config/main.php';
// remove the following lines when in production mode
defined('YII_DEBUG') or define('YII_DEBUG',true);
// specify how many levels of call stack should be shown in each log message
defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3);
require_once($yii);
Yii::createWebApplication($config)->run();
require_once($yii) 引入了全局类Yii,而类Yii只干了一件事:引入类YiiBase:
系统的全局访问都是通过Yii类(即YiiBase类)来实现的,Yii类的成员和方法都是static类型。
require(dirname(__FILE__).'/YiiBase.php');
class Yii extends YiiBase
{
}
继续追踪Yii的静态方法createWebApplication()发现它其实在内部调用并返回了CWebApplication类的一个全局对象实例,这个对象就是以后会大量使用到的Yii::app()返回的全局(以后简称app)对象,这是个唯一的实例(singleton),稍后详解。
public static function createWebApplication($config=null)
{
return self::createApplication('CWebApplication',$config);
}
public static function createApplication($class,$config=null)
{
return new $class($config); <-----既 new CWebApplication($config);
}
随即这里引出一个疑惑,YiiBase.php中并未引入CWebApplication类所在的路径,为何可以直接实例化新的对象并返回呢?这就是Yii利用PHP5提供的spl库来完成类的自动加载。在YiiBase.php 文件结尾处
spl_autoload_register(array('YiiBase','autoload'));
将YiiBase类的静态方法autoload 注册为类加载器。(此处省略,请自行Google学习spl_autoload_register方法的原理)系统将类名传递给被注册的类加载器函数,类加载器函数根据类名自行找到对应的类文件并include 。
public static function autoload($className,$classMapOnly=false)
{
// use include so that the error PHP file may appear
if(isset(self::$classMap[$className]))
include(self::$classMap[$className]);
elseif(isset(self::$_coreClasses[$className]))
include(YII_PATH.self::$_coreClasses[$className]);
elseif($classMapOnly)
return false;
else
...
return true;
}
可以看到YiiBase的静态成员$_coreClasses 数组里预先存放着Yii系统自身用到的类对应的文件路径:
private static $_coreClasses=array(
'CApplication' => '/base/CApplication.php',
'CApplicationComponent' => '/base/CApplicationComponent.php',
'CBehavior' => '/base/CBehavior.php',
'CComponent' => '/base/CComponent.php',
'CErrorEvent' => '/base/CErrorEvent.php',
'CErrorHandler' => '/base/CErrorHandler.php',
'CException' => '/base/CException.php',
...
...
}
非 coreClasse 的类注册会保存在YiiBase的$_imports数组及$_classMap中,通过Yii::import()方法将类路径导入PHP include paths中:
$classMap以键值对的形式保存注册类的类名及相对路径
// The array keys are the class names and the array values are the corresponding class file paths.
public static $classMap=array();
$_imports以别名(伪路径)或名字空间(namespace)形式保存要注册类的类名如别名方式application.components.GoogleMap引入GoogleMap类
或application.components.* 引入components目录下的所有类
Starting from version 1.1.5, this method can also be used to import a class in namespace format
如名字空间方式application\components\GoogleMap与上例中别名方式引入GoogleMap类功能相同,但此方法
要求名字空间中斜线所分隔的每个空间名所组成的目录层次是一个真实有效的路径以便Yii::import()方法找到对应的
类文件并引入
private static $_imports=array(); // alias => class name or directory
现在回到回到前面的程序入口的 Yii::createWebApplication($config)->run(); 现在autoload机制开始工作了。
当系统 执行 new CWebApplication() 的时候,会自动
include(YII_PATH.'/base/CApplication.php')
将main.php里的配置信息数组$config传递给CWebApplication创建出对象,并执行对象的run() 方法启动框架。
CWebApplication类的继承关系
CWebApplication -> CApplication -> CModule -> CComponent
$config先被传递给CApplication的构造函数
public function __construct($config=null)
{
Yii::setApplication($this); <-----之前说到的设置全局app对象的单例模式(singleton)
将自身的实例对象赋给Yii的静态成员$_app,以后可以通过 Yii::app() 来取得
// set basePath at early as possible to avoid trouble
if(is_string($config))
$config=require($config);
if(isset($config['basePath']))
{
$this->setBasePath($config['basePath']);
unset($config['basePath']);
}
else
$this->setBasePath('protected');
Yii::setPathOfAlias('application',$this->getBasePath());
Yii::setPathOfAlias('webroot',dirname($_SERVER['SCRIPT_FILENAME']));
if(isset($config['extensionPath']))
{
$this->setExtensionPath($config['extensionPath']);
unset($config['extensionPath']);
}
else
Yii::setPathOfAlias('ext',$this->getBasePath().DIRECTORY_SEPARATOR.'extensions');
if(isset($config['aliases']))
{
$this->setAliases($config['aliases']);
unset($config['aliases']);
}
$this->preinit();
$this->initSystemHandlers();
$this->registerCoreComponents(); <----1、注册系统核心组件
$this->configure($config); <----2、$config既全局配置文件main.php中配置文件
$this->attachBehaviors($this->behaviors);
$this->preloadComponents(); <----3、加载配置文件main.php中'preload'处列举的组件
$this->init();
}
1、$this->registerCoreComponents();
protected function registerCoreComponents()
{
$components=array(
'coreMessages'=>array(
'class'=>'CPhpMessageSource',
'language'=>'en_us',
'basePath'=>YII_PATH.DIRECTORY_SEPARATOR.'messages',
),
'db'=>array(
'class'=>'CDbConnection',
),
'messages'=>array(
'class'=>'CPhpMessageSource',
),
'urlManager'=>array(
'class'=>'CUrlManager',
),
...
...
),
$this->setComponents($components);
}
注册了几个系统组件(Components)。Components 是在 CModule 里定义和管理的,主要包括两个数组
private $_components=array(); <---Componemt的实例存放在$_components 数组里
private $_componentConfig=array();<---存放相关的配置信息,配置信息包括Component 的类名和属性设置
每个 Component 都是 IApplicationComponent接口的实例,
CWebApplication 对象注册了以下几个Component:
urlManager, request,session,assetManager,user,themeManager,authManager,clientScript。
CWebApplication的parent(CApplication) 注册了以下几个Component:
coreMessages,db,messages,errorHandler,securityManager,statePersister。
Component 在Yii PHP里是个非常重要的东西,它的特征是可以通过 CModule 的 __get() 和 __set() 方法来访问。 Component 注册的时候并不会创建对象实例,而是在程序里被第一次访问到的时候,由CModule 来负责(实际上就是 Yii::app())创建。详见CModule之类方法getComponent($id,$createIfNull=true)。
2、 $this->configure($config); configure() 还是在CModule 里:
public function configure($config)
{
if(is_array($config))
{
foreach($config as $key=>$value)
$this->$key=$value;
}
}
实际上是把$config数组里的每一项传给 CModule 的 父类 CComponent __set() 方法。
public function __set($name,$value)
{
$setter='set'.$name;
if(method_exists($this,$setter))
return $this->$setter($value);
elseif(strncasecmp($name,'on',2)===0 && method_exists($this,$name))
{
// duplicating getEventHandlers() here for performance
$name=strtolower($name);
if(!isset($this->_e[$name]))
$this->_e[$name]=new CList;
return $this->_e[$name]->add($value);
}
elseif(is_array($this->_m))
{
foreach($this->_m as $object)
{
if($object->getEnabled() && (property_exists($object,$name) || $object->canSetProperty($name)))
return $object->$name=$value;
}
}
if(method_exists($this,'get'.$name))
throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.',
array('{class}'=>get_class($this), '{property}'=>$name)));
else
throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',
array('{class}'=>get_class($this), '{property}'=>$name)));
}
我们来看看: if(method_exists($this,$setter))
根据这个条件,$config 数组里的basePath, params, modules, import, components 都被传递给相应的 setBasePath(), setParams() 等方法里进行处理。
未完待续...