ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
Yii PHP 框架分析(三) 2009-10-04 11:55 <table style="TABLE-LAYOUT: fixed; WIDTH: 100%"><tbody><tr><td><div class="cnt" id="blog_text"> <p><strong>Yii PHP 框架分析(三)<br/> 作者:wdy</strong></p> <p><strong><a href="http://hi.baidu.com/delphiss/blog/item/357663d152c0aa85a1ec9c44.html">http://hi.baidu.com/delphiss/blog/item/357663d152c0aa85a1ec9c44.html</a></strong></p> <p>Yii应用的入口脚本引用出了Yii类,Yii类的定义:</p> <p>class Yii extends YiiBase<br/> {<br/> }</p> <p>由yiic创建的应用里Yii类只是YiiBase类的“马甲”,我们也可以根据需求定制自己的Yii类。</p> <p>Yii(即YiiBase)是一个“helper class”,为整个应用提供静态和全局访问入口。</p> <p>Yii类的几个静态成员:<br/> $_aliases : 存放系统的别名对应的真实路径<br/> $_imports : <br/> $_classes :<br/> $_includePaths php include paths<br/> $_app : CWebApplication对象,通过 Yii::app() 访问到<br/> $_logger : 系统日志对象</p> <p>$_app 对象由 Yii::createWebApplication() 方法创建。</p> <p><strong>类自动加载</strong></p> <p>Yii基于php5的autoload机制来提供类的自动加载功能,自动加载器为YiiBase类的静态方法autoload()。</p> <p>当程序中用new创建对象或访问到类的静态成员,php将类名传递给类加载器,由类加载器完成类文件的include。</p> <p>autoload机制实现了类的“按需导入”,就是系统访问到类时才include类的文件。</p> <p>YiiBase类的静态成员$_coreClasses 里预先存放了Yii自身的核心类名于对应的类文件路径。其他的Yii应用中用到的类可以用Yii::import() 导入,Yii::import()将单类的与对应类文件存放于$_classes中,以*通配符表示的路径加入到php include_path中。被Yii::import()导入的类文件或目录都记入$_imports中,避免多次导入。</p> <p>/* Yii::import()<br/> * $alias: 要导入的类名或路径<br/> * $forceInclude false:只导入不include类文件,true则导入并include类文件<br/> */<br/> public static function import($alias,$forceInclude=false)<br/> {<br/>    // 先判断$alias是否存在于YiiBase::$_imports[] 中,已存在的直接return, 避免重复import。<br/>    if(isset(self::$_imports[$alias])) // previously imported<br/>     return self::$_imports[$alias];<br/>    // $alias类已定义,记入$_imports[],直接返回<br/>    if(class_exists($alias,false) || interface_exists($alias,false))<br/>     return self::$_imports[$alias]=$alias;<br/>    // 已定义于$_coreClasses[]的类,或名字中不含.的类,记入$_imports[],直接返回<br/>    if(isset(self::$_coreClasses[$alias]) || ($pos=strrpos($alias,'.'))===false) // a simple class name<br/>    {<br/>     self::$_imports[$alias]=$alias;<br/>     if($forceInclude)<br/>     {<br/>      if(isset(self::$_coreClasses[$alias])) // a core class<br/>       require(YII_PATH.self::$_coreClasses[$alias]);<br/>      else<br/>       require($alias.'.php');<br/>     }<br/>     return $alias;<br/>    }<br/>    // 产生一个变量 $className,为$alias最后一个.后面的部分<br/>    // 这样的:'x.y.ClassNamer'<br/>    // $className不等于 '*', 并且ClassNamer类已定义的,      ClassNamer' 记入 $_imports[],直接返回<br/>    if(($className=(string)substr($alias,$pos+1))!=='*' &amp;&amp; (class_exists($className,false) || interface_exists($className,false)))<br/>     return self::$_imports[$alias]=$className;<br/>    // $alias里含有别名,并转换真实路径成功<br/>    if(($path=self::getPathOfAlias($alias))!==false)<br/>    {<br/>     // 不是以*结尾的路径(单类)<br/>     if($className!=='*')<br/>     {<br/>      self::$_imports[$alias]=$className;<br/>      if($forceInclude)<br/>       require($path.'.php');<br/>      else<br/>       // 类名与真实路径记入$_classes数组<br/>       self::$_classes[$className]=$path.'.php';<br/>      return $className;<br/>     }<br/>     // $alias是'system.web.*'这样的已*结尾的路径,将路径加到include_path中<br/>     else // a directory<br/>     {<br/>      if(self::$_includePaths===null)<br/>      {<br/>       self::$_includePaths=array_unique(explode(PATH_SEPARATOR,get_include_path()));<br/>       if(($pos=array_search('.',self::$_includePaths,true))!==false)<br/>        unset(self::$_includePaths[$pos]);<br/>      }<br/>      array_unshift(self::$_includePaths,$path);<br/>      set_include_path('.'.PATH_SEPARATOR.implode(PATH_SEPARATOR,self::$_includePaths));<br/>      return self::$_imports[$alias]=$path;<br/>     }<br/>    }<br/>    else<br/>     throw new CException(Yii::t('yii','Alias "{alias}" is invalid. Make sure it points to an existing directory or file.',<br/>      array('{alias}'=&gt;$alias)));<br/> }</p> <p>然后看看 YiiBase::autoload() 函数的处理:</p> <p>public static function autoload($className)<br/> {<br/>    // $_coreClasses中配置好的类直接引入<br/>    if(isset(self::$_coreClasses[$className]))<br/>     include(YII_PATH.self::$_coreClasses[$className]);<br/>    // $_classes 中登记的单类直接引入<br/>    else if(isset(self::$_classes[$className]))<br/>     include(self::$_classes[$className]);<br/>    else<br/>    {<br/>     // 其他的认为文件路径以记入 include_path 里,以$className.'.php'直接引入<br/>     include($className.'.php');<br/>     return class_exists($className,false) || interface_exists($className,false);<br/>    }<br/>    return true;<br/> }</p> <p>系统配置文件里的 import 项里的类或路径在脚本启动中会被自动导入。用户应用里个别类需要引入的类可以在类定义前加入 Yii::import() 语句。</p> <p><strong>应用组件管理</strong></p> <p>前面提到Yii的CComponent类提供了组件的属性、事件、行为的访问接口,而CComponent的子类CModule更提供了应用组件(application components)的管理。</p> <p>应用组件必须是IApplicationComponen接口的实例,需要实现接口的init()和getIsInitialized()方法。init()会在应用组件初始化参数后被自动调用。</p> <p>Yii自身的功能模块都是通过应用组件的方式来提供的,比如常见的 Yii::app()-&gt;user, Yii::app()-&gt;request 等。用户也可以定义应用组件。</p> <p>作为 Yii::app() 对象(CWebApplication)的父类,CModule提供了完整的组件生命周期管理,包括组件的创建、初始化、对象存储等。</p> <p>每个应用组件用一个字符串名字来标识,通过CModule类的__get() 方法来访问。</p> <p>CModule类的$_components[] 成员存放应用组件的对象实例($name =&gt; $object),$_componentConfig[] 里存放应用组件的类名和初始化参数。</p> <p>使用应用组件的时候,先在$_componentConfig里设置好组件的类名和初始化参数,在第一次访问组件的时候,CModule会自动创建应用组件对象实例并初始化给定的参数,然后会调用应用组件的init()方法。</p> <p>Yii::app()对象的类CWebApplication及其父类CApplication预先配置了系统自身用到的应用组件:urlManager, request, session, assetManager, user, themeManager, authManager, clientScript, coreMessages, db, messages, errorHandler, securityManager, statePersister。</p> <p>我们可以再系统配置文件的components项目里修改系统应用组件的参数或配置新的应用组件。</p> <p>CModule并不负责应用组件实例的创建,而是由Yii::createComponent() 静态方法来完成的。</p> <p>createComponent()的参数$config 可以是类名的字符串或是存储了类名和初始化参数的数组。</p> <p><strong>应用组件的配置</strong></p> <p>应用组件的配置存储在系统$config变量中(config/main.php里)的components项里:</p> <p>// application components<br/> 'components'=&gt;array(<br/>    'log'=&gt;array(<br/>     'class'=&gt;'CLogRouter',<br/>     'routes'=&gt;array(<br/>      array(<br/>       'class'=&gt;'CFileLogRoute',<br/>       'levels'=&gt;'error, warning',<br/>      ),<br/>     ),<br/>    ),<br/>    'user'=&gt;array(<br/>     // enable cookie-based authentication<br/>     'allowAutoLogin'=&gt;true,<br/>    ),<br/> ),</p> <p>$config里的components项在CApplication的构造函数里被处理:</p> <p>$this-&gt;configure($config);</p> <p>configure()函数的处理很简单:</p> <p>public function configure($config)<br/> {<br/>    if(is_array($config))<br/>    {<br/>     foreach($config as $key=&gt;$value)<br/>      $this-&gt;$key=$value;<br/>    }<br/> }</p> <p>$config里的每一项被当做属性传给$_app对象的setXXX()属性设置方法,其中'components’项在CWebApplication的CModule的父类setComponents()处理。</p> <p>setComponents() 将'components’项里的类名及初始化参数存放到 $_componentConfig[]里:</p> <p>public function setComponents($components)<br/> {<br/>    // $config 里的’components’每一项<br/>    foreach($components as $id=&gt;$component)<br/>    {<br/>     if($component instanceof IApplicationComponent)<br/>      $this-&gt;setComponent($id,$component);<br/>     // $_componentConfig里已经存在配置,合并$component<br/>     else if(isset($this-&gt;_componentConfig[$id]))<br/>      $this-&gt;_componentConfig[$id]=CMap::mergeArray($this-&gt;_componentConfig[$id],$component);<br/>     // 在$_componentConfig里新建项目<br/>     else<br/>      $this-&gt;_componentConfig[$id]=$component;<br/>    }<br/> }</p> <p><br/><strong>应用组件的访问</strong></p> <p>CModule类重载了CComponent的__get()方法,优先访问应用组件对象。</p> <p>public function __get($name)<br/> {<br/>    if($this-&gt;hasComponent($name))<br/>     return $this-&gt;getComponent($name);<br/>    else<br/>     return parent::__get($name);<br/> }</p> <p>hasComponent() 判断$_components[]中是否已存在组件实例,或$_componentConfig[]中存在组件配置信息。</p> <p>public function hasComponent($id)<br/> {<br/>    return isset($this-&gt;_components[$id]) || isset($this-&gt;_componentConfig[$id]);<br/> }</p> <p>getComponent() 判断组件实例已经存在于$_components[]中,则直接返回对象。<br/> 否则根据$_componentConfig[]里的组件配置数据调用 Yii::createComponent() 来创建组件,并将对象存入$_components[]中然后返回。</p> <p>public function getComponent($id,$createIfNull=true)<br/> {<br/>    if(isset($this-&gt;_components[$id]))<br/>     return $this-&gt;_components[$id];<br/>    else if(isset($this-&gt;_componentConfig[$id]) &amp;&amp; $createIfNull)<br/>    {<br/>     $config=$this-&gt;_componentConfig[$id];<br/>     unset($this-&gt;_componentConfig[$id]);<br/>     if(!isset($config['enabled']) || $config['enabled'])<br/>     {<br/>      Yii::trace("Loading \"$id\" application component",'system.web.CModule');<br/>      unset($config['enabled']);<br/>      $component=Yii::createComponent($config);<br/>      $component-&gt;init();<br/>      return $this-&gt;_components[$id]=$component;<br/>     }<br/>    }<br/> }</p> <p><strong>应用组件的创建</strong></p> <p>Yii::createComponent() 来完成应用组件的创建</p> <p>public static function createComponent($config)<br/> {<br/>    if(is_string($config))<br/>    {<br/>     $type=$config;<br/>     $config=array();<br/>    }<br/>    else if(isset($config['class']))<br/>    {<br/>     $type=$config['class'];<br/>     unset($config['class']);<br/>    }<br/>    else<br/>     throw new CException(Yii::t('yii','Object configuration must be an array containing a "class" element.'));</p> <p>   if(!class_exists($type,false))<br/>     $type=Yii::import($type,true);</p> <p>   if(($n=func_num_args())&gt;1)<br/>    {<br/>     $args=func_get_args();<br/>     if($n===2)<br/>      $object=new $type($args[1]);<br/>     else if($n===3)<br/>      $object=new $type($args[1],$args[2]);<br/>     else if($n===4)<br/>      $object=new $type($args[1],$args[2],$args[3]);<br/>     else<br/>     {<br/>      unset($args[0]);<br/>      $class=new ReflectionClass($type);<br/>      // Note: ReflectionClass::newInstanceArgs() is available for PHP 5.1.3+<br/>      // $object=$class-&gt;newInstanceArgs($args);<br/>      $object=call_user_func_array(array($class,'newInstance'),$args);<br/>     }<br/>    }<br/>    else<br/>     $object=new $type;</p> <p>   foreach($config as $key=&gt;$value)<br/>     $object-&gt;$key=$value;</p> <p>   return $object;<br/> }</p> <p/> </div></td> </tr></tbody></table> |   | |-----|