zoukankan      html  css  js  c++  java
  • Yii PHP 框架分析(四)

    作者:wdy

    http://hi.baidu.com/delphiss/blog/item/c15b314f05f9dfc0d0c86a26.html

    Yii应用的入口脚本最后一句启动了WebApplication

    Yii::createWebApplication($config)->run();
    CApplication:
    public function run()
    {
       $this->onBeginRequest(new CEvent($this));
       $this->processRequest();
       $this->onEndRequest(new CEvent($this));
    }
    //processRequest()开始处理请求,由CWebApplication实现:
    public function processRequest()
    {
       if(is_array($this->catchAllRequest) && isset($this->catchAllRequest[0]))
       {
        $route=$this->catchAllRequest[0];
        foreach(array_splice($this->catchAllRequest,1) as $name=>$value)
         $_GET[$name]=$value;
       }
       else
        $route=$this->getUrlManager()->parseUrl($this->getRequest());
       $this->runController($route);
    }

    urlManager应用组件的parseUrl() 创建了$route (形式为controllerID/actionID的字符串),runController()创建Controller对象开始处理http请求。

    $route 的值可能存在以下几种情况:
    - 为空: 用 defaultController 值代替;
    - “moduleID/controllerID/actionID”: module下的
    - “controllerID/actionID” : 最常见的形式
    - “folder1/folder2/controllerID/actionID” 多级目录下的控制器

    runController首先调用createController()创建控制器对象

    public function createController($route,$owner=null)
    {
       // $owner为空则设置为$this,即 $_app对象
       if($owner===null)
        $owner=$this;
       // $route为空设置为defaultController,在$config里配置
       if(($route=trim($route,'/'))==='')
        $route=$owner->defaultController;
       $caseSensitive=$this->getUrlManager()->caseSensitive;
       $route.='/';
       // 逐一取出 $route 按 ‘/’分割后的第一段进行处理
       while(($pos=strpos($route,'/'))!==false)
       {
        // $id 里存放的是 $route 第一个 ‘/’前的部分
        $id=substr($route,0,$pos);
        if(!preg_match('/^w+$/',$id))
         return null;
        if(!$caseSensitive)
         $id=strtolower($id);
        // $route 存放’/’后面部分
        $route=(string)substr($route,$pos+1);
        if(!isset($basePath)) // 完整$route的第一段
        {
         // 如果$id在controllerMap[]里做了映射
         // 直接根据$id创建controller对象
         if(isset($owner->controllerMap[$id]))
         {
          return array(
           Yii::createComponent($owner->controllerMap[$id],$id,$owner===$this?null:$owner),
           $this->parseActionParams($route),
          );
         }
         // $id 是系统已定义的 module,根据$id取得module对象作为$owner参数来createController
         if(($module=$owner->getModule($id))!==null)
          return $this->createController($route,$module);
         // 控制器所在的目录
         $basePath=$owner->getControllerPath();
         $controllerID='';
        }
        else
         $controllerID.='/';
        $className=ucfirst($id).'Controller';
        $classFile=$basePath.DIRECTORY_SEPARATOR.$className.'.php';
        // 控制器类文件存在,则require并创建控制器对象&返回
        if(is_file($classFile))
        {
         if(!class_exists($className,false))
          require($classFile);
         if(class_exists($className,false) && is_subclass_of($className,'CController'))
         {
          $id[0]=strtolower($id[0]);
          return array(
           new $className($controllerID.$id,$owner===$this?null:$owner),
           $this->parseActionParams($route),
          );
         }
         return null;
        }
        // 未找到控制器类文件,可能是多级目录,继续往子目录搜索
        $controllerID.=$id;
        $basePath.=DIRECTORY_SEPARATOR.$id;
       }
    }

    createController() 返回一个创建好的控制器对象和actionID, runController()调用控制器的init()方法和run($actionID)来运行控制器:

    public function runController($route)
    {
       if(($ca=$this->createController($route))!==null)
       {
        list($controller,$actionID)=$ca;
        $oldController=$this->_controller;
        $this->_controller=$controller;
        $controller->init();
        $controller->run($actionID);
        $this->_controller=$oldController;
       }
       else
        throw new CHttpException( 404, Yii::t('yii','Unable to resolve the request "{route}".', array( '{route}'=>$route==='' ? $this->defaultController:$route)));
    }
    $controller->init()里没有动作, run():
    public function run($actionID)
    {
       if(($action=$this->createAction($actionID))!==null)
       {
        if(($parent=$this->getModule())===null)
         $parent=Yii::app();
        if($parent->beforeControllerAction($this,$action))
        {
         $this->runActionWithFilters($action,$this->filters());
         $parent->afterControllerAction($this,$action);
        }
       }
       else
        $this->missingAction($actionID);
    }
    $controller->run($actionID)里首先创建了Action对象:
    public function createAction($actionID)
    {
       // 为空设置为defaultAction
       if($actionID==='')
        $actionID=$this->defaultAction;
       // 控制器里存在 'action'.$actionID 的方法,创建CInlineAction对象
       if(method_exists($this,'action'.$actionID) && strcasecmp($actionID,'s')) // we have actions method
        return new CInlineAction($this,$actionID);
       // 否则根据actions映射来创建Action对象
       else
        return $this->createActionFromMap($this->actions(),$actionID,$actionID);
    }

    这里可以看到控制器并不是直接调用了action方法,而是需要一个Action对象来运行控制器动作,这样就统一了控制器方法和actions映射的action对象对action的处理,即两种形式的action处理都统一为IAction接口的run()调用。

    IAction接口要求实现run(),getId(),getController () 三个方法,Yii提供的CAction类要求构造函数提供Controller和Id并实现了getId()和getController ()的处理,Action类从CAction继承即可。

    CInlineAction在web/action下,run()是很简单的处理过程,调用了Controller的action方法:

    class CInlineAction extends CAction
    {
    public function run()
    {
       $method='action'.$this->getId();
       $this->getController()->$method();
    }
    }
    //回到 $controller->run($actionID)
    public function run($actionID)
    {
       if(($action=$this->createAction($actionID))!==null)
       {
        if(($parent=$this->getModule())===null)
         $parent=Yii::app();
        if($parent->beforeControllerAction($this,$action))
        {
         $this->runActionWithFilters($action,$this->filters());
         $parent->afterControllerAction($this,$action);
        }
       }
       else
        $this->missingAction($actionID);
    }

    Yii::app()->beforeControllerAction() 实际是固定返回true的,所以action对象实际是通过控制器的runActionWithFilters()被run的

    public function runActionWithFilters($action,$filters)
    {
       // 控制器里没有设置过滤器
       if(empty($filters))
        $this->runAction($action);
       else
       {
        // 创建过滤器链对象并运行
        $priorAction=$this->_action;
        $this->_action=$action;
        CFilterChain::create($this,$action,$filters)->run();
        $this->_action=$priorAction;
       }
    }

    没有过滤器,runAction()就是最终要调用前面创建的action对象的run()方法:

    public function runAction($action)
    {
       $priorAction=$this->_action;
       $this->_action=$action;
       if($this->beforeAction($action))
       {
        $action->run();
        $this->afterAction($action);
       }
       $this->_action=$priorAction;
    }

    每个filter都要实现IFilter接口,filter实现的preFilter()方法在$action->run()之前调用,如果判断action可以执行则返回true,否则返回false

    if($filter1->preFilter())
    if($filter2->preFilter())
       if($filtern->preFilter())
        $action->run()
        $filtern->postFilter()
       $filter2->postFilter()
    $filter1->postFilter()

    在action里最常见的操作就是render view文件: renderPartial()和render()。render()在处理view文件后会把结果放入layout文件内。

    public function renderPartial($view,$data=null,$return=false,$processOutput=false)
    {
       if(($viewFile=$this->getViewFile($view))!==false)
       {
        $output=$this->renderFile($viewFile,$data,true);
        if($processOutput)
         $output=$this->processOutput($output);
        if($return)
         return $output;
        else
         echo $output;
       }
       else
        throw new CException(Yii::t('yii','{controller} cannot find the requested view "{view}".',
         array('{controller}'=>get_class($this), '{view}'=>$view)));
    }

    getViewFile($view)获得$view的完整路径:
    $view 以 ‘/’开头的,以系统views目录作为起始目录+$view+.php
    $view含有别名的,查找别名的真实路径
    其他的以modele view目录作为起始目录+$view+.php

    如果没有在$config里配置第三方的renderer,renderFile() 里实际是调用了yii自身提供的renderInternal()来render view文件:

    public function renderFile($viewFile,$data=null,$return=false)
    {
       $widgetCount=count($this->_widgetStack);
       // 如果配置了其他的ViewRenderer
       if(($renderer=Yii::app()->getViewRenderer())!==null)
        $content=$renderer->renderFile($this,$viewFile,$data,$return);
       else
        // yii 自身的render
        $content=$this->renderInternal($viewFile,$data,$return);
       if(count($this->_widgetStack)===$widgetCount)
        return $content;
       else
       {
        $widget=end($this->_widgetStack);
        throw new CException(Yii::t('yii','{controller} contains improperly nested widget tags in its view "{view}". A {widget} widget does not have an endWidget() call.',
         array('{controller}'=>get_class($this), '{view}'=>$viewFile, '{widget}'=>get_class($widget))));
       }
    }

    Yii的renderer用的是php本身作为模板系统:

    public function renderInternal($_viewFile_,$_data_=null,$_return_=false)
    {
       // extract函数将$_data_从数组中将变量导入到当前的符号表
       if(is_array($_data_))
        extract($_data_,EXTR_PREFIX_SAME,'data');
       else
        $data=$_data_;
       if($_return_)
       {
        ob_start();
        ob_implicit_flush(false);
        require($_viewFile_);
        return ob_get_clean();
       }
       else
        require($_viewFile_);
    }

    render()的实际上是先renderPartial view文件,然后renderFile layoutfile,并将view文件的结果做为$content变量传入。

    public function render($view,$data=null,$return=false)
    {
       $output=$this->renderPartial($view,$data,true);
       if(($layoutFile=$this->getLayoutFile($this->layout))!==false)
        $output=$this->renderFile($layoutFile,array('content'=>$output),true);
       $output=$this->processOutput($output);
       if($return)
        return $output;
       else
        echo $output;
    }

    processOutput将render的结果再做处理,比如在head加上css或js脚本等。

    public function processOutput ($output)
    {
       Yii::app()->getClientScript()->render($output);
       // if using page caching, we should delay dynamic output replacement
       if($this->_dynamicOutput!==null && $this->isCachingStackEmpty())
        $output=$this->processDynamicOutput($output);
       if($this->_pageStates===null)
        $this->_pageStates=$this->loadPageStates();
       if(!empty($this->_pageStates))
        $this->savePageStates($this->_pageStates,$output);
       return $output;
    }
  • 相关阅读:
    Ant构建原理及build.xml文档描述
    Selenium WebDriver的工作原理
    appium工作原理
    jmeter获取mysql数据并作为请求参数使用
    linux sar的使用
    hdu 1520 Anniversary party
    hdu 1331 Function Run Fun
    hdu 1208 Pascal's Travels
    hdu 1159 Common Subsequence
    poj 1129 Channel Allocation
  • 原文地址:https://www.cnblogs.com/imxiu/p/3414249.html
Copyright © 2011-2022 走看看