php – 一个关于在对象方法中require文件的有意思的地方

在php的文件包含,一般都是引入类文件,有点像加入类库的方式,现在探讨的是一种在php对象方法中require文件的特色。

在对象A中的a()方法中,require文件B,那么,在B文件中是可以使用$this->xxx(),

验证:我们在test/文件夹下面新建了两个文件  index.php  和 test.php

index.php文件内容如下:

<?php
class test{
  
  public function index(){
    
    echo 111;
    $this->test1();
  }
  public function test1(){
    require("./test.php");
  }
  public function xx(){
    
    echo 3333;
  }
}
$test = new test();
$test->index();

test.php:

fdssafsdafads
<?php
 echo $this->xx();
?>

访问 http://xx.com/test/index.php

可以看到输出结果:

111fdssafsdafads 3333

也就是说,$this 就是$test对象,

参看文件:yii2 页面功能块配置实现原理(前端+后端提供数据类),以及Views深度嵌套

结合yii2 view的利用ob函数生成view的原理,就会更加透彻。

yii2 页面功能块配置实现原理(前端+后端提供数据类),以及Views深度嵌套

在用yii2做大型网站的时候,尤其是做产品的时候,譬如做电商系统,在很多页面的侧栏,或者内容部分的底部等部分,我们希望通过配置的方式,很容易的把某个块加入,譬如在分类页面限制客户的浏览产品记录和newsletter,在产品页面,我们同样想显示这些,我们可能做出来很多这样的显示独立块,让客户在后台通过配置的方式就可以加入这些块,我们需要考虑的如下

  1. 这些块由两部分组成,数据提供者block部分,view html部分,通过block提供的动态数据,画出整个页面。
  2. 当前功能块可能包含其他的功能块,也就是多个功能块嵌套。
  3. 实现上述功能,我们就可以做出来很多独立功能块,在各个页面很容易的配置。下面是实现的方法

我的yii2 fec扩展已经实现这个功能,github地址:https://github.com/fancyecommerce/yii2-fec

下面讲述的是实现原理。

  1. 这个是实现的原理文件:
<?php
namespace fec\helpers;
use Yii;
use yii\base\View;
use yii\base\InvalidConfigException;
class CView
{	
  # 功能块:
  # 本功能的作用是通过一个类文件,和一个view 文件,生成一个html块,而且在这个html中还可以嵌套其他的块
  # 这样设计的好处:譬如在前端,我们在很多url中都会有一些公用的侧栏块,我们很希望我们的块可以通过配置的方式很容易的加入到侧栏
  # 譬如电商网站侧栏的:客户的浏览记录,我们在多个页面都想加入这个功能,我们就可以很方便的做加入。
  
  # 默认的方法,功能块的提供数据的默认方法。可以在配置中配置method方法自定义。
  const DATA_METHOD = 'getLastData';
  
  /*  你可以在view 文件中通过下面的方式使用
    <?php

      use fec\helpers\CView;
      $config = [
        # 必填
        'class' => 'fec\block\TestMenu',
        'view'  => '@fec/views/testmenu/index.php',
        # 下面为选填
        'method'=> 'getLastData',
        'terry1'=> 'My1',
        'terry2'=> 'My2',
      ];
      echo CView::getChildHtml($config)
      ?>
  */
    public static function getChildHtml($config)
    {
    if(!isset($config['class']) || empty($config['class'])
    || !isset($config['view']) || empty($config['view'])
    ){
      throw new InvalidConfigException('view and class must exist in array config!');
    }
    $method = self::DATA_METHOD;
    if(isset($config['method']) && !empty($config['method'])){
      $method = $config['method'];
      unset($config['method']);
    }
    $view = $config['view'];
    unset($config['view']);
    $ob = Yii::createObject($config);
    $params = $ob->$method();
    return Yii::$app->view->render($view, $params);
    
    }
  # 通过配置
  /*
  1.add config param to modules params or application params.
  params.php
  [
    'params' => [
      'block' =>[
        'menu' =>[
          # 必填
          'class' => 'fec\block\TestMenu',
          'view'  => '@fec/views/testmenu/index.php',
          # 下面为选填
          'method'=> 'getLastData',
          'terry1'=> 'My1',
          'terry2'=> 'My2',
        ],
      ]
    ]
  ]
  2.
  use fec\helpers\CView;
  CView::getConfigChildHtml('menu');
  */
  public static function getConfigChildHtml($configKey){
    $config = [];
    # get config from module param 
    if($module = Yii::$app->controller->module){
      $module_config = CModule::param("block");
      if(isset($module_config[$configKey])){
        $config = $module_config[$configKey];
      }
    }
    # if module config param is empty or not exist,
    # get config from application
    if(empty($config)){
      $app_config = CConfig::param("block");
      if(isset($app_config[$configKey])){
        $config = $app_config[$configKey];
      }
    }
    
    if(!isset($config['class']) || empty($config['class'])
    || !isset($config['view']) || empty($config['view'])
    ){
      throw new InvalidConfigException('view and class must exist in array config!');
    }else{
      return self::getChildHtml($config);
    }
    
  }
}

2.新建功能块的block文件:

<?php
namespace fec\block;
use Yii;

class TestMenu
{
  public $terry1;
  public $terry2;
  
    public function getLastData()
    {
        $arr = [
      'terry1' =>$this->terry1,
      'terry2' =>$this->terry2,
    ];
        return $arr;
    }
}

3.新建view显示部分:

<div>My Name is
<?php

  echo $terry1."_".$terry2;
?>

</div>

4.在其他的view文件中调用:

<?php

use fec\helpers\CView;
$config = [
  'class' => 'fec\block\TestMenu',
  'view'  => '@fec/views/testmenu/index.php',
  # 下面为选填
  'method'=> 'getLastData',
  'terry1'=> 'My111',
  'terry2'=> 'My222',
];
echo CView::getChildHtml($config);
?>

 

也就是通过上面的配置数组指定block的提供者, view文件的实现文件,也就是上面的步骤 2 和3,method为选填,如果不填写,默认为getLastData函数,terry1,terry2为初始化提供变量,然后我们就可以看到输出了

My Name is My111_My222

5.我们通过配置的方式调用CView::getConfigChildHtml()函数

5.1 加入module配置:(在report 配置中)

<?php
return [
  'report' => [
    'class' => 'appadmin\code\Report\Module',
    
    'components'=>[
      'mycomponent' => [
        'class' => 'appadmin\component\MyComponent',
        'terry' => 'xxxx',
      ],
    ],
    
    'params' => [
      'water' => 'good',
      'block' =>[
        'menu' =>[
          # 必填
          'class' => 'fec\block\TestMenu',
          'view'  => '@fec/views/testmenu/index.php',
          # 下面为选填
          'method'=> 'getLastData',
          'terry1'=> 'My1',
          'terry2'=> 'My2',
        ],
      ]
    ],
  ],
];

当然,您如果不使用模块,可以在params.php中直接加入block的配置部分:

'params' => [
      'water' => 'good',
      'block' =>[
        'menu' =>[
          # 必填
          'class' => 'fec\block\TestMenu',
          'view'  => '@fec/views/testmenu/index.php',
          # 下面为选填
          'method'=> 'getLastData',
          'terry1'=> 'My1',
          'terry2'=> 'My2',
        ],
      ]
    ],

如果application 和module同时配置,那么在当前module下面,module下面的配置有限,

使用:

<?php

use fec\helpers\CView;

echo CView::getConfigChildHtml('menu')
?>
My Name is My1_My2

总结:这个功能是可以深度嵌套,譬如在view1.php调用了child -> menu,

在menu的view文件中又调用了child–>product ,…..,深入嵌套,这是魅力所在。

 

 

yii2 使用redis queue

在fec插件中已经实现queue

fec插件:https://github.com/fancyecommerce/yii2-fec

在使用之前,您需要先进行配置如下(注意,添加到console中):

'components' => [
      'queue' => [
          'class' => 'fec\component\RedisQueue',
      ],
  ],
  
  'controllerMap' => [
      'queue' => 'fec\component\redisqueue\QueueController'
  ],

使用方法:

1.首先建立一个Job类,实现run方法:

譬如:\fec\component\redisqueue\TestJob

<?php
namespace fec\component\redisqueue;

class TestJob
{
    public function run($job, $data)
    {
        //process $data;
        var_dump($data);
    }
}

2. 执行命令行,设置监听,监听到任务,就执行。

这里一般是多台主机,一起过来拿任务,因此这里一般是多台主机,配置的都是同一个redis。从redis过来拿数据。

主机1:

./yii queue/listen MyTestQueue

主机2:

./yii queue/listen MyTestQueue

3. 往序列里面添加任务:

$job = '\fec\component\redisqueue\TestJob';  #任务类,实现了run方法
$data = ['a', 'b', 'c'];   # 参数
$queue  = 'MyTestQueue';   # queueName,这里和命令行哪里的名字对应
CRedisQuery::push($job,$data,$queue);  # 开始加任务。

执行后,可以看到命令行处,有刚才添加的任务。

Yii2 Gii Console部分 – 探究解析生成代码原理

Gii 是一个蛮不错的学习Module

console 生成model的解析:

  1. gii的config配置
* return [
 *     'bootstrap' => ['gii'],
 *     'modules' => [
 *         'gii' => ['class' => 'yii\gii\Module'],
 *     ],
 * ]

也就说,在初始化的时候需要执行Module的bootstrap方法:

public function bootstrap($app)
    {
        if ($app instanceof \yii\web\Application) {
            $app->getUrlManager()->addRules([
                $this->id => $this->id . '/default/index',
                $this->id . '/<id:\w+>' => $this->id . '/default/view',
                $this->id . '/<controller:[\w\-]+>/<action:[\w\-]+>' => $this->id . '/<controller>/<action>',
            ], false);
        } elseif ($app instanceof \yii\console\Application) {
            $app->controllerMap[$this->id] = [
                'class' => 'yii\gii\console\GenerateController',
                'generators' => array_merge($this->coreGenerators(), $this->generators),
                'module' => $this,
            ];
        }
    }

如果是console的话,就设置app的controllerMap。也就是console的Gii/访问路径:

yii\gii\console\GenerateController。

传递参数:generators:

protected function coreGenerators()
    {
        return [
            'model' => ['class' => 'yii\gii\generators\model\Generator'],
            'crud' => ['class' => 'yii\gii\generators\crud\Generator'],
            'controller' => ['class' => 'yii\gii\generators\controller\Generator'],
            'form' => ['class' => 'yii\gii\generators\form\Generator'],
            'module' => ['class' => 'yii\gii\generators\module\Generator'],
            'extension' => ['class' => 'yii\gii\generators\extension\Generator'],
        ];
    }

还有一部分就是config配置中对gii的 generators 参数的配置,也就是可以自定义gii了。

生成model的命令为:

./yii gii/model --tableName=city --modelClass=City

也就是找到 yii\gii\console\GenerateController。 去找actionModel()方法,但是这个方法不存在,那么,在actions中查找

public function actions()
   {
       $actions = [];
       foreach ($this->generators as $name => $generator) {
           $actions[$name] = [
               'class' => 'yii\gii\console\GenerateAction',
               'generator' => $generator,
           ];
       }
       return $actions;
   }

从上面可以看出来,就是通过GenerateAction这个class,根据穿入的generator的不同实例化不同的参数。这个generator参数就是上面的配置

 'model' => ['class' => 'yii\gii\generators\model\Generator'],

下面我们查看这个文件

yii\gii\console\GenerateAction

的run方法:

public function run()
    {
        echo "Running '{$this->generator->getName()}'...\n\n";

        if ($this->generator->validate()) {
            $this->generateCode();
        } else {
            $this->displayValidationErrors();
        }
    }

也就是代码:

$this->generateCode();
protected function generateCode()
    {
        $files = $this->generator->generate();
        $n = count($files);
        if ($n === 0) {
            echo "No code to be generated.\n";
            return;
        }
        echo "The following files will be generated:\n";
        $skipAll = $this->controller->interactive ? null : !$this->controller->overwrite;
        $answers = [];
        foreach ($files as $file) {
            $path = $file->getRelativePath();
            if (is_file($file->path)) {
                if (file_get_contents($file->path) === $file->content) {
                    echo '  ' . $this->controller->ansiFormat('[unchanged]', Console::FG_GREY);
                    echo $this->controller->ansiFormat(" $path\n", Console::FG_CYAN);
                    $answers[$file->id] = false;
                } else {
                    echo '    ' . $this->controller->ansiFormat('[changed]', Console::FG_RED);
                    echo $this->controller->ansiFormat(" $path\n", Console::FG_CYAN);
                    if ($skipAll !== null) {
                        $answers[$file->id] = !$skipAll;
                    } else {
                        $answer = $this->controller->select("Do you want to overwrite this file?", [
                            'y' => 'Overwrite this file.',
                            'n' => 'Skip this file.',
                            'ya' => 'Overwrite this and the rest of the changed files.',
                            'na' => 'Skip this and the rest of the changed files.',
                        ]);
                        $answers[$file->id] = $answer === 'y' || $answer === 'ya';
                        if ($answer === 'ya') {
                            $skipAll = false;
                        } elseif ($answer === 'na') {
                            $skipAll = true;
                        }
                    }
                }
            } else {
                echo '        ' . $this->controller->ansiFormat('[new]', Console::FG_GREEN);
                echo $this->controller->ansiFormat(" $path\n", Console::FG_CYAN);
                $answers[$file->id] = true;
            }
        }

        if (!array_sum($answers)) {
            $this->controller->stdout("\nNo files were chosen to be generated.\n", Console::FG_CYAN);
            return;
        }

        if (!$this->controller->confirm("\nReady to generate the selected files?", true)) {
            $this->controller->stdout("\nNo file was generated.\n", Console::FG_CYAN);
            return;
        }

        if ($this->generator->save($files, (array) $answers, $results)) {
            $this->controller->stdout("\nFiles were generated successfully!\n", Console::FG_GREEN);
        } else {
            $this->controller->stdout("\nSome errors occurred while generating the files.", Console::FG_RED);
        }
        echo preg_replace('%<span class="error">(.*?)</span>%', '\1', $results) . "\n";
    }

 

 $files = $this->generator->generate();

generator就是上面的:

 'model' => ['class' => 'yii\gii\generators\model\Generator'],

也就是这个文件的generate()方法返回的内容,就是文件内容:

public function generate()
    {
        $files = [];
        $relations = $this->generateRelations();
        $db = $this->getDbConnection();
        foreach ($this->getTableNames() as $tableName) {
            // model :
            $modelClassName = $this->generateClassName($tableName);
            $queryClassName = ($this->generateQuery) ? $this->generateQueryClassName($modelClassName) : false;
            $tableSchema = $db->getTableSchema($tableName);
            $params = [
                'tableName' => $tableName,
                'className' => $modelClassName,
                'queryClassName' => $queryClassName,
                'tableSchema' => $tableSchema,
                'labels' => $this->generateLabels($tableSchema),
                'rules' => $this->generateRules($tableSchema),
                'relations' => isset($relations[$tableName]) ? $relations[$tableName] : [],
            ];
            $files[] = new CodeFile(
                Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $modelClassName . '.php',
                $this->render('model.php', $params)
            );

            // query :
            if ($queryClassName) {
                $params = [
                    'className' => $queryClassName,
                    'modelClassName' => $modelClassName,
                ];
                $files[] = new CodeFile(
                    Yii::getAlias('@' . str_replace('\\', '/', $this->queryNs)) . '/' . $queryClassName . '.php',
                    $this->render('query.php', $params)
                );
            }
        }

        return $files;
    }

生成文件的部分:

 $this->render('model.php', $params)

public function render($template, $params = [])
    {
        $view = new View();
        $params['generator'] = $this;

        return $view->renderFile($this->getTemplatePath() . '/' . $template, $params, $this);
    }

在这里可以看到,还是用的view的renderFile来生成的:

public function renderFile($viewFile, $params = [], $context = null)
   {
       $viewFile = Yii::getAlias($viewFile);

       if ($this->theme !== null) {
           $viewFile = $this->theme->applyTo($viewFile);
       }
       if (is_file($viewFile)) {
           $viewFile = FileHelper::localize($viewFile);
       } else {
           throw new InvalidParamException("The view file does not exist: $viewFile");
       }

       $oldContext = $this->context;
       if ($context !== null) {
           $this->context = $context;
       }
       $output = '';
       $this->_viewFiles[] = $viewFile;

       if ($this->beforeRender($viewFile, $params)) {
           Yii::trace("Rendering view file: $viewFile", __METHOD__);
           $ext = pathinfo($viewFile, PATHINFO_EXTENSION);
           if (isset($this->renderers[$ext])) {
               if (is_array($this->renderers[$ext]) || is_string($this->renderers[$ext])) {
                   $this->renderers[$ext] = Yii::createObject($this->renderers[$ext]);
               }
               /* @var $renderer ViewRenderer */
               $renderer = $this->renderers[$ext];
               $output = $renderer->render($this, $viewFile, $params);
           } else {
               $output = $this->renderPhpFile($viewFile, $params);
           }
           $this->afterRender($viewFile, $params, $output);
       }

       array_pop($this->_viewFiles);
       $this->context = $oldContext;

       return $output;
   }

最终生成内容。

下面查看对应的model.php文件:

yii2-gii/generators/model/default/model.php

<?php
/**
 * This is the template for generating the model class of a specified table.
 */

/* @var $this yii\web\View */
/* @var $generator yii\gii\generators\model\Generator */
/* @var $tableName string full table name */
/* @var $className string class name */
/* @var $queryClassName string query class name */
/* @var $tableSchema yii\db\TableSchema */
/* @var $labels string[] list of attribute labels (name => label) */
/* @var $rules string[] list of validation rules */
/* @var $relations array list of relations (name => relation declaration) */

echo "<?php\n";
?>

namespace <?= $generator->ns ?>;

use Yii;

/**
 * This is the model class for table "<?= $generator->generateTableName($tableName) ?>".
 *
<?php foreach ($tableSchema->columns as $column): ?>
 * @property <?= "{$column->phpType} \${$column->name}\n" ?>
<?php endforeach; ?>
<?php if (!empty($relations)): ?>
 *
<?php foreach ($relations as $name => $relation): ?>
 * @property <?= $relation[1] . ($relation[2] ? '[]' : '') . ' $' . lcfirst($name) . "\n" ?>
<?php endforeach; ?>
<?php endif; ?>
 */
class <?= $className ?> extends <?= '\\' . ltrim($generator->baseClass, '\\') . "\n" ?>
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return '<?= $generator->generateTableName($tableName) ?>';
    }
<?php if ($generator->db !== 'db'): ?>

    /**
     * @return \yii\db\Connection the database connection used by this AR class.
     */
    public static function getDb()
    {
        return Yii::$app->get('<?= $generator->db ?>');
    }
<?php endif; ?>

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [<?= "\n            " . implode(",\n            ", $rules) . "\n        " ?>];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
<?php foreach ($labels as $name => $label): ?>
            <?= "'$name' => " . $generator->generateString($label) . ",\n" ?>
<?php endforeach; ?>
        ];
    }
<?php foreach ($relations as $name => $relation): ?>

    /**
     * @return \yii\db\ActiveQuery
     */
    public function get<?= $name ?>()
    {
        <?= $relation[0] . "\n" ?>
    }
<?php endforeach; ?>
<?php if ($queryClassName): ?>
<?php
    $queryClassFullName = ($generator->ns === $generator->queryNs) ? $queryClassName : '\\' . $generator->queryNs . '\\' . $queryClassName;
    echo "\n";
?>
    /**
     * @inheritdoc
     * @return <?= $queryClassFullName ?> the active query used by this AR class.
     */
    public static function find()
    {
        return new <?= $queryClassFullName ?>(get_called_class());
    }
<?php endif; ?>
}

也就是一个模板文件生成而来。

OK,到这里就完成了流程的查看了。

yii2 通过 render , views页面生成显示html的原理

在controller里面有一个 render 函数

$this->render($this->action->id,['xxx'=>'3333']);

也就是通过一个render函数,把view文件和对应的参数数组传递进去,然后生成对应的html返回,这是怎么实现的呢?

这里用到的是php 的ob系列函数:

public function renderPhpFile($_file_, $_params_ = [])    
  {    
      ob_start();    
      ob_implicit_flush(false);    
      extract($_params_, EXTR_OVERWRITE);    
      require($_file_);    
      return ob_get_clean();    
  }

如果我们想嵌套,也就是一个ob函数里面有其他的ob函数,可以这样:

public function dd($i){    
        $i--;    
        if($i > 0){    
            ob_start();    
            echo "test".$i;    
            $str = ob_get_clean();    
            return $str.$this->dd($i);    
        }else{    
            return 22;    
        }    
    }    
       
    public function actionIndex(){    
            
         ob_start();    
     
        echo "Hellon/"; //输出     
    
    
        echo $this->dd(6);    
                    
            
        ob_end_flush();//输出全部内容到浏览器     
        exit;    
}

这样就可以吧一些东西做成tab html,譬如侧栏的联系我们,newsletter,等一些div框,就可以做成tab的方式,通过配置加载进来实现。

yii2 初始化的bootstrap过程 -引导

bootstrap是指应用开始解析并处理请求之前,一个预先准备环境的阶段,也就是一个前期的初始化 过程,也就是说,在执行helloworld之前,需要执行的代码部分。

启动这个过程会在两个地方,一个是index.php的入口文件,一个是application的bootstrap过程。

1.index.php入口文件: 大致为composer文件自动加载器,yii的文件自动加载器,已经配置文件的合并,环境参数的设置。然后通过\yii\base\application->run()方法创建一个应用主体application。

2.

  • 调用 yii\base\Application::preInit()(预初始化)方法,配置一些高优先级的应用属性,比如 yii\base\Application::basePath 属性。
  • 注册yii\base\Application::errorHandler。
  • 通过给定的应用配置初始化应用的各属性。
  • 通过调用 yii\base\Application::init()(初始化)方法,它会顺次调用 yii\base\Application::bootstrap() 从而运行引导组件。
    • 加载扩展清单文件(extension manifest file) vendor/yiisoft/extensions.php
    • 创建并运行各个扩展声明的 引导组件(bootstrap components)。
    • 创建并运行各个 应用组件 以及在应用的 Bootstrap 属性中声明的各个 模块(modules)组件(如果有)。

总体来说就是:配置参数,配置error的处理,然后执行application的bootstrap()方法

application的bootstrap方法依次:

1.加载插件配置,也就是文件:@vendor/yiisoft/extensions.php中的bootstrap配置,

2.配置的组件(component)和模块的bootstrap配置。

application的bootstrap代码如下:

protected function bootstrap()
   {
       if ($this->extensions === null) {
           $file = Yii::getAlias('@vendor/yiisoft/extensions.php');
           $this->extensions = is_file($file) ? include($file) : [];
       }
       foreach ($this->extensions as $extension) {
           if (!empty($extension['alias'])) {
               foreach ($extension['alias'] as $name => $path) {
                   Yii::setAlias($name, $path);
               }
           }
           if (isset($extension['bootstrap'])) {
               $component = Yii::createObject($extension['bootstrap']);
               if ($component instanceof BootstrapInterface) {
                   Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
                   $component->bootstrap($this);
               } else {
                   Yii::trace('Bootstrap with ' . get_class($component), __METHOD__);
               }
           }
       }

       foreach ($this->bootstrap as $class) {
           $component = null;
           if (is_string($class)) {
               if ($this->has($class)) {
                   $component = $this->get($class);
               } elseif ($this->hasModule($class)) {
                   $component = $this->getModule($class);
               } elseif (strpos($class, '\\') === false) {
                   throw new InvalidConfigException("Unknown bootstrapping component ID: $class");
               }
           }
           if (!isset($component)) {
               $component = Yii::createObject($class);
           }

           if ($component instanceof BootstrapInterface) {
               Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
               $component->bootstrap($this);
           } else {
               Yii::trace('Bootstrap with ' . get_class($component), __METHOD__);
           }
       }
   }

先执行extensions的,在一次执行在bootstrap中的配置,按照配置的顺序依次执行

2.1:在插件扩展中加入bootstrap

'fancyecommerce/fec' => 
 array (
   'name' => 'fancyecommerce/fec',
   'version' => '1.1.2.1',
   'bootstrap' => array
   'alias' => 
   array (
     '@fec' => $vendorDir . '/fancyecommerce/fec',
   ),
   'bootstrap': 'fecadmin\\mywidget\\MyBootstrapClass'
 ),
namespace fecadmin\mywidget;

use yii\base\BootstrapInterface;
use yii\base\Application;

class MyBootstrapClass implements BootstrapInterface
{
    public function bootstrap($app)
    {
        $app->on(Application::EVENT_BEFORE_REQUEST, function () {
             // do something here
        });
    }
}

这样就会在bootstrap过程中执行这个类里面的bootstrap()方法:

参考文献:http://www.yiiframework.com/doc-2.0/guide-structure-extensions.html#bootstrapping-classes,在这里可以查看如何在插件制作的时候在composer.json中进行配置,配置后的内容将被写入extensions.php文件中。

2.2 在模块中加入bootstrap配置

如果是模块,则执行模块对应的Module文件,

$config['bootstrap'][] = 'gii';
$config['modules']['gii'] = 'yii\gii\Module';

上面的配置,配置了模块 gii,并且把gii加入了bootstrap,因此,就会执行\yii\gii\Module->bootstrap();

gii的bootstrap()为:

class Module extends \yii\base\Module implements BootstrapInterface
{
public function bootstrap($app)
   {
       if ($app instanceof \yii\web\Application) {
           $app->getUrlManager()->addRules([
               $this->id => $this->id . '/default/index',
               $this->id . '/<id:\w+>' => $this->id . '/default/view',
               $this->id . '/<controller:[\w\-]+>/<action:[\w\-]+>' => $this->id . '/<controller>/<action>',
           ], false);
       } elseif ($app instanceof \yii\console\Application) {
           $app->controllerMap[$this->id] = [
               'class' => 'yii\gii\console\GenerateController',
               'generators' => array_merge($this->coreGenerators(), $this->generators),
               'module' => $this,
           ];
       }
   }

修改app对应的UrlManager 和controllerMap。

需要注意的是要想bootstrap()执行,一定要实现BootstrapInterface接口,否则,不会被执行,因为在Application->bootstrap函数中有一个判断:

if ($component instanceof BootstrapInterface) {

      Yii::trace('Bootstrap with ' . get_class($component) . '::bootstrap()', __METHOD__);
      $component->bootstrap($this);
}

 

2.3 如果该配置是组件,则执行组件文件对应的class里面的bootstrap方法

同样,和上面的module类型,需要做的是:

2.3.1 在config.php中加入配置,譬如log:

'bootstrap' => ['log'],

2.3.2 让组件所在的class实现接口:use yii\base\BootstrapInterface;

2.3.3  在组件所在的class实现方法bootstrap($app)

这样就可以了。

 

 

yii2 Url 自定义 伪静态url

这里说的伪静态url,其实就是自定义url,

譬如:www.fancyecommerce.com/fashion-handbag-women.html

这个urlkey   对应的fashion-handbag-women.html 是没有这个文件的。但是为了seo,需要把url改成这个样子,也就是url自定义,这个,我们需要保存这个url到数据库,对应一个yii2中的urlPath以及对应的参数。

譬如在数据库中保存:fashion-handbag-women.html 对应 /report/order/index?sort=id&p=3

当访问www.fancyecommerce.com/fashion-handbag-women.html这个url的时候,实际加载的是www.fancyecommerce.com/report/order/index?sort=id&p=3。

现在说的是实现思路:

在入口文件,我们可以看到:

$application = new yii\web\Application($config);
$application->run();

 

查找run方法:yii\base\Application

public function run()
    {
        try {

            $this->state = self::STATE_BEFORE_REQUEST;
            $this->trigger(self::EVENT_BEFORE_REQUEST);

            $this->state = self::STATE_HANDLING_REQUEST;
            $response = $this->handleRequest($this->getRequest());

            $this->state = self::STATE_AFTER_REQUEST;
            $this->trigger(self::EVENT_AFTER_REQUEST);

            $this->state = self::STATE_SENDING_RESPONSE;
            $response->send();

            $this->state = self::STATE_END;

            return $response->exitStatus;

        } catch (ExitException $e) {

            $this->end($e->statusCode, isset($response) ? $response : null);
            return $e->statusCode;

        }
    }

在这里我们可以看到代码

$response = $this->handleRequest($this->getRequest());

然后查找方法:

public function handleRequest($request)
    {
        if (empty($this->catchAll)) {
            list ($route, $params) = $request->resolve();
        } else {
            $route = $this->catchAll[0];
            $params = $this->catchAll;
            unset($params[0]);
        }
        try {
            Yii::trace("Route requested: '$route'", __METHOD__);
            $this->requestedRoute = $route;
            $result = $this->runAction($route, $params);
            if ($result instanceof Response) {
                return $result;
            } else {
                $response = $this->getResponse();
                if ($result !== null) {
                    $response->data = $result;
                }

                return $response;
            }
        } catch (InvalidRouteException $e) {
            throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'), $e->getCode(), $e);
        }
    }

上面的代码第四行为resolue方法,我们去yii\web\Request查看该方法

$request->resolve();
public function resolve()
    {
        $result = Yii::$app->getUrlManager()->parseRequest($this);

然后我们去yii\web\UrlManager中查看。parseRequest()

public function parseRequest($request)
    {
        if ($this->enablePrettyUrl) {
            $pathInfo = $request->getPathInfo();

也就找到代码  $pathInfo = $request->getPathInfo()方法,如下:

public function getPathInfo()
    {
        if ($this->_pathInfo === null) {
            $this->_pathInfo = $this->resolvePathInfo();
        }

然后我们找到resolvePathInfo方法,如下:

protected function resolvePathInfo()
    {
        $pathInfo = $this->getUrl();

找到getUrl方法,如下:

public function getUrl()
    {
        if ($this->_url === null) {
            $this->_url = $this->resolveRequestUri();
        }

        return $this->_url;
    }

找到resolveRequestUri方法,如下:

protected function resolveRequestUri()
    {
        if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // IIS
            $requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
        } elseif (isset($_SERVER['REQUEST_URI'])) {
            $requestUri = $_SERVER['REQUEST_URI'];
            if ($requestUri !== '' && $requestUri[0] !== '/') {
                $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
            }
        } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
            $requestUri = $_SERVER['ORIG_PATH_INFO'];
            if (!empty($_SERVER['QUERY_STRING'])) {
                $requestUri .= '?' . $_SERVER['QUERY_STRING'];
            }
        } else {
            throw new InvalidConfigException('Unable to determine the request URI.');
        }

        return $requestUri;
    }

到这里,我们基本找到根源了,我们可以在这里插入代码,但是我们不能直接修改源码,所以,我们需要新建一个class,然后通过配置修改:

二:详细的代码:component中加入组件

'request' => [
      'class' => 'common\web\Request',
      'enableCookieValidation' => true,
      'enableCsrfValidation' => true,
      'cookieValidationKey' => 'O1d232trde1x-M97_7QvwPo-5QGdkLMp#@#@',
      'noCsrfRoutes' => [
        'catalog/product/addreview',
                'favorite/product/remark',
        'paypal/ipn/index',
        'paypal/ipn',
      ],
    ],

创建文件common\web\Request

protected function resolveRequestUri()
    {
        if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // IIS
            $requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
        } elseif (isset($_SERVER['REQUEST_URI'])) {
            $requestUri = $_SERVER['REQUEST_URI'];
            if ($requestUri !== '' && $requestUri[0] !== '/') {
                $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
            }
        } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
            $requestUri = $_SERVER['ORIG_PATH_INFO'];
            if (!empty($_SERVER['QUERY_STRING'])) {
                $requestUri .= '?' . $_SERVER['QUERY_STRING'];
            }
        } else {
            throw new InvalidConfigException('Unable to determine the request URI.');
        }
    # 修改代码如下:
    # return $requestUri;
    # 改为:
    return $this->getRewriteUri($requestUri);
    }

定义getRewriteUri方法:代码如下:

##重写url ,根据得到的url,在 $rewrite_url_arr 中查找是否存在 ,如果存在,
  ##说明这个url是自定义的url,需要更改成真实的url, 更改RequestUri为原来的url
  protected function getRewriteUri($requestUri){
    $front_requestUri = "";
    $wh_requestUri = "";
    $dh_requestUri = "";
    if(strstr($requestUri,"?")){
      $arr = explode("?",$requestUri);
      $front_requestUri = $arr[0];
      $wh_requestUri = $arr[1];
    }else if(strstr($requestUri,"#")){
      $arr = explode("#",$requestUri);
      $front_requestUri = $arr[0];
      $dh_requestUri = $arr[1];
    }else{
      $front_requestUri = $requestUri;
    }
    //echo $requestUri;exit;
    if($custom_uri = $this->getCustomUrl($front_requestUri)){
      if($wh_requestUri){
        return $custom_uri."?".$wh_requestUri;
      }else if($dh_requestUri){
        return $custom_uri."#".$dh_requestUri;
      }else{
        return $custom_uri;
      } 
    }else{
      return $requestUri;
    }
    
  }
  #根据当前的自定义uri,得到数据库保存的真实uri。
  protected function getCustomUrl($uri){
    # 去掉头部的/
    if(substr($uri,0,1) == "/"){
      $uri = substr($uri,1);
    }
    $url_rewrite_coll = \Yii::$app->mongodb->getCollection('url_rewrite');
    $one = $url_rewrite_coll->findOneConvert(['request_path' => $uri]);
    if($one['_id']){
      $type = $one['type'];
      $type_data_id = $one['type_data_id'];
      Global $page_handle;
      if($type == 'product'){
        $product_coll = \Yii::$app->mongodb->getCollection("catalog_product");
        $where = ["_id"=>(int)$type_data_id,"status"=>1 ];
        if(!Config::param("out_stock_product_is_show")){
          $where['is_in_stock'] = 1;
        }
        $product_data = $product_coll->findOneConvert($where);
        if($product_data['_id']){
          $page_handle = $product_data;
          return '/catalog/product/index';
        }
      }else if($type == 'category'){
        $category_table = "catalog_category";
        $category_coll = \Yii::$app->mongodb->getCollection($category_table);
        
        $category_data = $category_coll->findOneConvert(["_id"=>(int)$type_data_id,"status"=>1 ]);
        if($category_data['_id']){
          $page_handle = $category_data;
          return '/catalog/category/index';
        }
      }else if($type == 'cmspage'){
        $cmspage_coll = \Yii::$app->mongodb->getCollection("cms_page");
        $cmspage_data = $cmspage_coll->findOneConvert(["_id"=>(int)$type_data_id,"status"=>1 ]);
        if($cmspage_data['_id']){
          $page_handle = $cmspage_data;
          return '/home/index/page';
        }
      # 下一步做。blog没有做。
      }else if($type == 'blog'){
        $blog_coll = \Yii::$app->mongodb->getCollection("blog_article");
        $blog_data = $blog_coll->findOneConvert(["_id"=>(int)$type_data_id,"status"=>1 ]);
        if($blog_data['_id']){
          $page_handle = $blog_data;
          return '/blog/blog/index';
        }
      }else if($type == 'blogcategory'){
        $blogcategory_coll = \Yii::$app->mongodb->getCollection("blog_category");
        $blogcategory_data = $blogcategory_coll->findOneConvert(["_id"=>(int)$type_data_id ]);
        if($blogcategory_data['_id']){
          $page_handle = $blogcategory_data;
          return '/blog/category/index';
        }
      }else{
        return false;
      }
    }else{
      $rewrite_url_arr = array(
        'contacts'=>'customer/contacts/index',
      );
      if($rewrite_url_arr[$uri]){
        return "/".$rewrite_url_arr[$uri];
      }
      return false;
    }
    
    
    
  }

yii2 自定义 Url ,到这里基本已经说完, 关于yii2的 伪静态url,还需要在nginx哪里做设置:

当然,我的这个定义,是按照我自己的逻辑来的,大家可以根据自己的方式来

为了加速,这个部分最好是放到redis里面,快速查询。

yii2 添加 自定义 组件 custom component,以及模块 module 原理的详解剖析

本文主要说的是两种情况:

1.在yii2中自定义全局组件:yii2 custom component

2. yii2模块中自定义组件的实现方式:yii2  module  custom component

yii2中定义了很多的组件,我们可以自己添加一个组件,步骤如下:

1.建立文件MyComponent.php,也即是我们自定义的组件,

<?php
namespace app\component;
use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
 
class MyComponent extends Component
{
  
  public $terry;
  
  public function welcome()
  {
    echo $this->terry."Hello..Welcome to MyComponent";
  }
 
}

custom component ,自定义组件。

2.添加配置,在config.php文件的component中:

'mycomponent' => [
     'class' => 'appadmin\component\MyComponent',
     'terry' => 'xxxx',
 ],

3.调用:

Yii::$app->mycomponent->welcome();

可以看到以下的输出:xxxxHello..Welcome to MyComponent1

4.另外我们希望在模块里面配置自定义的组件

模块 module 原理的详解剖析:

这里需要说一下模块,对于没有模块的时候,我们的主体是application,这个类继承与\yii\base\Component,  在某种程序上module 就是一个微缩版的application,应用主体有的功能,譬如参数,组件等配置,module都可以使用,在使用前我们需要先得到module。得到module的方式有如下两种:

# 通过moduleName得到module
$module = Yii::$app->getModule($moduleName)
# 得到当前的module。
$module = Yii::$app->controller->module

得到了module,我们就可以像使用application那样使用,譬如应用主体下面使用组件:

$app = Yii::$app;
$component = $app->mycomponent;

而模块里面使用为:
$module = Yii::$app->controller->module
$component = $module->mycomponent;

#在模块中获取配置参数:
$param = $module->params[$param];
#在上面可以看出,在模块中的方法的使用,和应用主体中使用很类似
$module 相当于  Yii::$app

在模块中配置参数:

您可以在模块的配置中添加,在params参数中添加,譬如如下:

'modules'=>[
    'report' => [
        'class' => 'appadmin\code\Report\Module',
    
        'components'=>[
          'mycomponent' => [
            'class' => 'appadmin\component\MyComponent',
            'terry' => 'xxxx',
          ],
        ],
    
        'params' => [
          'water' => 'good',
        ],
    ],
],

 

也可以在Module.php文件的init中添加params

Yii::configure($this, ['params'=> $params_data]);

 

下面我们写一下在模块里面配置组件的例子:

1.模块的定义:

'modules'=>[
    'report' => [
        'class' => 'appadmin\code\Report\Module',
    
        'components'=>[
          'mycomponent' => [
            'class' => 'appadmin\component\MyComponent',
            'terry' => 'xxxx',
          ],
        ],
    
        'params' => [
          'water' => 'good',
        ],
    ],
],

2.组件文件定义:

<?php
namespace appadmin\component;
use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
 
class MyComponent extends Component
{
  
  public $terry;
  
  public function welcome1()
  {
    $module_param_water = Yii::$app->controller->module->params['water'];
    echo $this->terry."Hello..Welcome to MyComponent ".$module_param_water;
  }
 
}

3.调用:

$module = Yii::$app->controller->module;
$module->mycomponent->welcome();
//Yii::$app->mycomponent->welcome();
echo 1;exit;

可以看到输出:

xxxxHello..Welcome to MyComponent tttttttt1

4.在其他模块调用Report模块的组件:

$module = Yii::$app->getModule("report");
$module->mycomponent->welcome();
//Yii::$app->mycomponent->welcome();
echo 1;exit;

输出结果为:

xxxxHello..Welcome to MyComponent 1

可以看到结果与上面的不同。这是因为welcome1函数定义:

$module_param_water = Yii::$app->controller->module->params['water'];

module_param_water 是当前模块下面的配置,而在当前module下面没有配置这个模块,因此,这个值为空。

 

下面是模块的module文件的对params的配置方法:用来合并config.php里面param的配置,和在模块文件里面./etc/config.php文件里面的配置。

public function configModuleParams(){
    # 配置config文件
    $config_file_dir = $this->_currentDir . '/etc/config.php';
    if(file_exists($config_file_dir)){
      $params_data = (require($config_file_dir));
      
    }
    # 设置参数
    $params_data['_currentDir'] 		= $this->_currentDir;
    $params_data['_currentNameSpace'] 	= $this->_currentNameSpace;
    $params = $this->params;
    if(is_array($params) && !empty($params)){
      $params_data = \yii\helpers\ArrayHelper::merge($params,$params_data);
    }
    Yii::configure($this, ['params'=> $params_data]);
    
  }

到这里,我们可以看到yii2 module的原理,以及细致的详解与剖析