Yii PHP 框架分析 (一)

http://hi.baidu.com/iwangdy/item/cc7c2cef6a5618d5ea34c98e

基于yii1.0.8的代码分析的。用了一个下午整理的,流水账,感兴趣的凑合着先看,国庆期间推出个整理修改版,然后再完成后两个部分(MVC和Yii的整体结构分析)。

1. 启动

网站的唯一入口程序 index.php :


1
2
3
4
5
6
$yii =dirname( __FILE__ ). '/../framework/yii.php' ;
$config =dirname( __FILE__ ). '/protected/config/main.php' ;
// remove the following line when in production mode
defined( 'YII_DEBUG' or  define( 'YII_DEBUG' ,true);
require_once ( $yii );
Yii::createWebApplication( $config )->run();

上面的require_once($yii) 引用出了后面要用到的全局类Yii,Yii类是YiiBase类的完全继承:

class Yii extends YiiBase
{
}

系统的全局访问都是通过Yii类(即YiiBase类)来实现的,Yii类的成员和方法都是static类型。

2. 类加载

Yii利用PHP5提供的spl库来完成类的自动加载。在YiiBase.php 文件结尾处

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

将YiiBase类的静态方法autoload 注册为类加载器。 PHP autoload 的简单原理就是执行 new 创建对象或通过类名访问静态成员时,系统将类名传递给被注册的类加载器函数,类加载器函数根据类名自行找到对应的类文件并include 。

下面是YiiBase类的autoload方法:

1
2
3
4
5
6
7
8
9
10
public  static  function  autoload( $className )
{
     // use include so that the error PHP file may appear
     if (isset(self:: $_coreClasses [ $className ]))
      include (YII_PATH.self:: $_coreClasses [ $className ]);
     else  if (isset(self:: $_classes [ $className ]))
      include (self:: $_classes [ $className ]);
     else
      include ( $className . '.php' );
}

可以看到YiiBase的静态成员$_coreClasses 数组里预先存放着Yii系统自身用到的类对应的文件路径:

private static $_coreClasses=array(
    'CApplication' => '/base/CApplication.php',
    'CBehavior' => '/base/CBehavior.php',
    'CComponent' => '/base/CComponent.php',
    ...
)

非 coreClasse 的类注册在YiiBase的$_classes 数组中:
private static $_classes=array();

其他的类需要用Yii::import()讲类路径导入PHP include paths 中,直接
include($className.'.php')

3. CWebApplication的创建

回到前面的程序入口的 Yii::createWebApplication($config)->run();

1
2
3
4
public  static  function  createWebApplication( $config =null)
{
     return  new  CWebApplication( $config );
}

现在autoload机制开始工作了。
当系统 执行 new CWebApplication() 的时候,会自动 
include(YII_PATH.'/base/CApplication.php')

将main.php里的配置信息数组$config传递给CWebApplication创建出对象,并执行对象的run() 方法启动框架。

CWebApplication类的继承关系

CWebApplication -> CApplication -> CModule -> CComponent

$config先被传递给CApplication的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public  function  __construct( $config =null)
{
     Yii::setApplication( $this );
     // 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' ]));
     $this ->preinit();
     $this ->initSystemHandlers();
     $this ->registerCoreComponents();
     $this ->configure( $config );
     $this ->attachBehaviors( $this ->behaviors);
     $this ->preloadComponents();
     $this ->init();
}


Yii::setApplication($this); 将自身的实例对象赋给Yii的静态成员$_app,以后可以通过 Yii::app() 来取得。
后面一段是设置CApplication 对象的_basePath ,指向 proteced 目录。

Yii::setPathOfAlias('application',$this->getBasePath());
Yii::setPathOfAlias('webroot',dirname($_SERVER['SCRIPT_FILENAME']));

设置了两个系统路径别名 application 和 webroot,后面再import的时候可以用别名来代替实际的完整路径。别名配置存放在YiiBase的 $_aliases 数组中。

$this->preinit();
预初始化。preinit()是在 CModule 类里定义的,没有任何动作。

$this->initSystemHandlers() 方法内容:

1
2
3
4
5
6
7
8
9
10
/**
* Initializes the class autoloader and error handlers.
*/
protected  function  initSystemHandlers()
{
     if (YII_ENABLE_EXCEPTION_HANDLER)
      set_exception_handler( array ( $this , 'handleException' ));
     if (YII_ENABLE_ERROR_HANDLER)
      set_error_handler( array ( $this , 'handleError' ), error_reporting ()); 
}

设置系统exception_handler和 error_handler,指向对象自身提供的两个方法。

4. 注册核心组件

$this->registerCoreComponents();
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
protected  function  registerCoreComponents()
{
     parent::registerCoreComponents();
     $components = array (
      'urlManager' => array (
       'class' => 'CUrlManager' ,
      ),
      'request' => array (
       'class' => 'CHttpRequest' ,
      ),
      'session' => array (
       'class' => 'CHttpSession' ,
      ),
      'assetManager' => array (
       'class' => 'CAssetManager' ,
      ),
      'user' => array (
       'class' => 'CWebUser' ,
      ),
      'themeManager' => array (
       'class' => 'CThemeManager' ,
      ),
      'authManager' => array (
       'class' => 'CPhpAuthManager' ,
      ),
      'clientScript' => array (
       'class' => 'CClientScript' ,
      ),
     );
     $this ->setComponents( $components );
}

注册了几个系统组件(Components)。
Components 是在 CModule 里定义和管理的,主要包括两个数组

private $_components=array();
private $_componentConfig=array();

每个 Component 都是 IApplicationComponent接口的实例,Componemt的实例存放在$_components 数组里,相关的配置信息存放在$_componentConfig数组里。配置信息包括Component 的类名和属性设置。

CWebApplication 对象注册了以下几个Component:urlManager, request,session,assetManager,user,themeManager,authManager,clientScript。CWebApplication的parent 注册了以下几个Component:coreMessages,db,messages,errorHandler,securityManager,statePersister。

Component 在YiiPHP里是个非常重要的东西,它的特征是可以通过 CModule 的 __get() 和 __set() 方法来访问。 Component 注册的时候并不会创建对象实例,而是在程序里被第一次访问到的时候,由CModule 来负责(实际上就是 Yii::app())创建。

5. 处理 $config 配置

继续, $this->configure($config);
configure() 还是在CModule 里:

1
2
3
4
5
6
7
8
public  function  configure( $config )
{
     if ( is_array ( $config ))
     {
      foreach ( $config  as  $key => $value )
       $this -> $key = $value ;
     }
}

实际上是把$config数组里的每一项传给 CModule 的 父类 CComponent __set() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public  function  __set( $name , $value )
{
     $setter = 'set' . $name ;
     if (method_exists( $this , $setter ))
      $this -> $setter ( $value );
     else  if ( 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;
       $this ->_e[ $name ]->add( $value );
      }
      else  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() 等方法里进行处理。

6、$config 之 import

其中 import 被传递给 CModule 的 setImport:

1
2
3
4
5
public  function  setImport( $aliases )
{
     foreach ( $aliases  as  $alias )
      Yii::import( $alias );
}

Yii::import($alias)里的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public  static  function  import( $alias , $forceInclude =false)
{
     // 先判断$alias是否存在于YiiBase::$_imports[] 中,已存在的直接return, 避免重复import。
     if (isset(self:: $_imports [ $alias ]))  // previously imported
      return  self:: $_imports [ $alias ];
     // $alias类已定义,记入$_imports[],直接返回
     if ( class_exists ( $alias ,false))
      return  self:: $_imports [ $alias ]= $alias ;
     // 类似 urlManager 这样的已定义于$_coreClasses[]的类,或不含.的直接类名,记入$_imports[],直接返回
     if (isset(self:: $_coreClasses [ $alias ]) || ( $pos = strrpos ( $alias , '.' ))===false) // a simple class name
     {
      self:: $_imports [ $alias ]= $alias ;
      if ( $forceInclude )
      {
       if (isset(self:: $_coreClasses [ $alias ]))  // a core class
        require (YII_PATH.self:: $_coreClasses [ $alias ]);
       else
        require ( $alias . '.php' );
      }
      return  $alias ;
     }
     // 产生一个变量 $className,为$alias最后一个.后面的部分
     // 这样的:'x.y.ClassNamer'
     // $className不等于 '*', 并且ClassNamer类已定义的,       ClassNamer' 记入 $_imports[],直接返回
     if (( $className =(string) substr ( $alias , $pos +1))!== '*'  &&  class_exists ( $className ,false))
      return  self:: $_imports [ $alias ]= $className ;
     // 取得 $alias 里真实的路径部分并且路径有效
     if (( $path =self::getPathOfAlias( $alias ))!==false)
     {
      // $className!=='*',$className 记入 $_imports[]
      if ( $className !== '*' )
      {
       self:: $_imports [ $alias ]= $className ;
       if ( $forceInclude )
        require ( $path . '.php' );
       else
        self:: $_classes [ $className ]= $path . '.php' ;
       return  $className ;
      }
      // $alias是'system.web.*'这样的已*结尾的路径,将路径加到include_path中
      else  // a directory
      {
       set_include_path(get_include_path().PATH_SEPARATOR. $path );
       return  self:: $_imports [ $alias ]= $path ;
      }
     }
     else
      throw  new  CException(Yii::t( 'yii' , 'Alias "{alias}" is invalid. Make sure it points to an existing directory or file.' ,
       array ( '{alias}' => $alias )));
}

7. $config 之 components

$config 数组里的 $components 被传递给CModule 的setComponents($components)

1
2
3
4
5
6
7
8
9
10
11
12
public  function  setComponents( $components )
{
     foreach ( $components  as  $id => $component )
     {
      if ( $component  instanceof  IApplicationComponent)
       $this ->setComponent( $id , $component );
      else  if (isset( $this ->_componentConfig[ $id ]))
       $this ->_componentConfig[ $id ]=CMap::mergeArray( $this ->_componentConfig[ $id ], $component );
      else
       $this ->_componentConfig[ $id ]= $component ;
     }
}

$componen是IApplicationComponen的实例的时候,直接赋值:
$this->setComponent($id,$component),

1
2
3
4
5
6
public  function  setComponent( $id , $component )
{
     $this ->_components[ $id ]= $component ;
     if (! $component ->getIsInitialized())
      $component ->init();
}

如果$id已存在于_componentConfig[]中(前面注册的coreComponent),将$component 属性加进入。
其他的component将component属性存入_componentConfig[]中。

8. $config 之 params

这个很简单

1
2
3
4
5
6
public  function  setParams( $value )
{
     $params = $this ->getParams();
     foreach ( $value  as  $k => $v )
      $params ->add( $k , $v );
}

configure 完毕!

9. attachBehaviors

$this->attachBehaviors($this->behaviors);
空的,没动作

预创建组件对象
$this->preloadComponents();

1
2
3
4
5
protected  function  preloadComponents()
{
     foreach ( $this ->preload  as  $id )
      $this ->getComponent( $id );
}

getComponent() 判断_components[] 数组里是否有 $id的实例,如果没有,就根据_componentConfig[$id]里的配置来创建组件对象,调用组件的init()方法,然后存入_components[$id]中。

10. init()

$this->init();

函数内:$this->getRequest();
创建了Reques 组件并初始化。

11. run()

1
2
3
4
5
6
public  function  run()
{
     $this ->onBeginRequest( new  CEvent( $this ));
     $this ->processRequest();
     $this ->onEndRequest( new  CEvent( $this ));
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值