zoukankan      html  css  js  c++  java
  • YII 的源码分析(二)

    上一篇简单分析了一下yii的流程,从创建一个应用,到屏幕上输出结果。这一次我来一个稍复杂一点的,重点在输出上,不再是简单的一行"hello world",而是要经过view(视图)层的处理。

    依然是demos目录,这次我们选择hangman,一个简单的猜字游戏。

    和helloworld应用相比,这次多了main.php,打开main看下源码:

    <?php
    
    return array(
        'name'=>'Hangman Game',
        'defaultController'=>'game',
        'components'=>array(
            'urlManager'=>array(
                'urlFormat'=>'path',
                'rules'=>array(
                    'game/guess/<g:w>'=>'game/guess',
                ),
            ),
        ),
    );

    在我们以后的实际项目中,也是经常要用到配置文件的,所以我觉得有必要了解一下yii的配置文件--main.php

    'name'=>'这里通常是定义网站的标题',也就是我们打开index.php时,在网页上显示的标题。

    'defaultController'=>'这里是默认的控制器',也就是我们的index.php后面没有指定控制器时系统采用的控制器,如果我们这里没有指出来,默认就是site

    'components'=>'这里是组件的参数,用多维数组进行配置。' 具体的参数可以查看yii手册。

    Yii::createWebApplication($config)->run(); 上一次我们已经详细分析过它了,这里就不再走一遍了。

    上次我们没有配置过程,所以$this->configure($config)什么也没有做,但是这次有配置参数,所以我们进去看看yii做了哪些操作:

    CApplication自己没有实现configure方法,是继承于CModule.php的:

        public function configure($config)
        {
            if(is_array($config))
            {
                foreach($config as $key=>$value)
                    $this->$key=$value;
            }
        }

    代码非常简单,就是把配置参数的键做为类的属性名,value做为类的属性值进行了扩展。

    由于url是index.php,后面没有任何参数,所以都是走的默认控制器,也就是我们在main.php中设定的game. 所以$controller 就等于 controllers/gameController.php, 通过上次的源码分析我们可以知道,在gameController.php中没有init方法时,都是走的父类中定义的默认方法(实际上是一个空方法),

    $controller->run($actionID); == gameController->run(''); 

    前面已经分析过了,没有指定时,都是默认参数。那么此时的$actionID为空,actionID就是gameController中定义的默认动作:public $defaultAction='play'; 

    runActionWithFilters --->  runAction --> $action->runWithParams

    走了这么多过程,和hello world的流程是差不多的。据上次的分析可以知道,这里执行了

    $controller->$methodName(); 也就是GameController->actionPlay()

    到此,我们本节的重点才真正开始:
        public function actionPlay()
        {
            static $levels=array(
                '10'=>'Easy game; you are allowed 10 misses.',
                '5'=>'Medium game; you are allowed 5 misses.',
                '3'=>'Hard game; you are allowed 3 misses.',
            );
    
            // if a difficulty level is correctly chosen
            if(isset($_POST['level']) && isset($levels[$_POST['level']]))
            {
                $this->word=$this->generateWord();
                $this->guessWord=str_repeat('_',strlen($this->word));
                $this->level=$_POST['level'];
                $this->misses=0;
                $this->setPageState('guessed',null);
                // show the guess page
                $this->render('guess');
            }
            else
            {
                $params=array(
                    'levels'=>$levels,
                    // if this is a POST request, it means the level is not chosen
                    'error'=>Yii::app()->request->isPostRequest,
                );
                // show the difficulty level page
                $this->render('play',$params);
            }
        }
    重点请看 $this->render('play',$params); 这个render方法这么面熟,很多框架中都有类似的方法,比如discuz,smarty,CI 等等. 纵观yii框架,render 在它整个MVC模式中,是V得以实现的重要骨干。所以有必要把它翻个底朝天。
        public function render($view,$data=null,$return=false)
        {
            if($this->beforeRender($view))
            {
                $output=$this->renderPartial($view,$data,true);
                if(($layoutFile=$this->getLayoutFile($this->layout))!==false)
                    $output=$this->renderFile($layoutFile,array('content'=>$output),true);
    
                $this->afterRender($view,$output);
    
                $output=$this->processOutput($output);
    
                if($return)
                    return $output;
                else
                    echo $output;
            }
        }

    先看$output=$this->renderPartial($view,$data,true); 从字面上来看,就是渲染局部视图。

        public function renderPartial($view,$data=null,$return=false,$processOutput=false)
        {
    
            if(($viewFile=$this->getViewFile($view))!==false)
            {
          //$viewFile=yiidemoshangmanprotectedviewsgameplay.php
    $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))); }

    这里调用了renderFile来处理,这个renderfile哪里来的,要自己学会分析继承关系,不明白的看第一篇,我这里直接上源码了:

        public function renderFile($viewFile,$data=null,$return=false)
        {
            $widgetCount=count($this->_widgetStack);
            if(($renderer=Yii::app()->getViewRenderer())!==null && $renderer->fileExtension==='.'.CFileHelper::getExtension($viewFile)){
                $content=$renderer->renderFile($this,$viewFile,$data,$return);
            }
            else{
                $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))));
            }
        }

    逻辑上走的是renderInternal方法:

        public function renderInternal($_viewFile_,$_data_=null,$_return_=false)
        {
            // we use special variable names here to avoid conflict when extracting 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_);
        }

    为什么要用ob_start() ? 这个方法比较有意思。它非常巧妙的把play.php视图中的html的内容和php输出的内容组装在了一起。然后整个return 出去,非常值得借鉴的思想。

    接着,我们再回到render。走余下的过程。

    $output=$this->renderFile($layoutFile,array('content'=>$output),true);
    这里直接又调用了一次renderFile方法,这个$layoutFile是什么呢?就是yiidemoshangmanprotectedviewslayoutsmain.php
    通过上次的分析,$output就是main.php的内容了,不过呢,是结合了play.php的内容。这又是怎么做到的呢?
    这就是extract($_data_,EXTR_PREFIX_SAME,'data');所发挥的功能了。
    还有一个processOutput方法,输出前进行一些缓存什么的,这里先不深究它。

    最后就是echo $output;因此屏幕上就有内容显示出来了。 

    也就是我们在index.php上最终看到的内容了。本次渲染比较简单,但是该走的逻辑都差不多用到了。

    总结一下:视图层,是通过render方法进行渲染输出的。而render实际上是分成两部分来做的,一部分是局部视图,另一部分是通用视图。

    类似于如下结构:

    <html><!--这里是通用视图-->
    <head>
    <title>xxx</title>
    </head>
    <body>
                        {{这里是局部视图内容}}
    </body>
    </html>

    render输出的是两者融合的内容。这样做的好处也是显而易见的,避免创建重复内容。

  • 相关阅读:
    N天学习一个linux命令之lsof
    N天学习一个linux命令之ps
    N天学习一个linux命令之yum
    N天学习一个linux命令之rsync
    N天学习一个linux命令之ss
    N天学习一个linux命令之netstat
    N天学习一个linux命令之vmstat
    N天学习一个linux命令之sort
    N天学习一个linux命令之rpm
    跨域问题
  • 原文地址:https://www.cnblogs.com/afrog/p/4156598.html
Copyright © 2011-2022 走看看