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的原理,以及细致的详解与剖析