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

 

YII2 – application 应用主体

在yii的入口脚本中我们可以看到如下代码:

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

从这里开始应用初始化,当然,除了web的应用,还有console的application

yii\console\Application。

那么,我们做一个功能的时候如何区分当前是web还是console呢?

可以通过如下的方法:

if(\Yii::$app instanceof  \yii\web\Application){

}else if(\Yii::$app instanceof  \yii\console\Application){

}

 

通过如上代码可以辨别应用属于哪个应用

yii2 cron job 插件使用

1.当我们的系统比较庞大,有很多脚本的时候,如果都写到crontab中会比较大,而且在程序方面不是很友好,现在介绍的是在程序中实现cron

插件的地址为:https://github.com/DenisOgr/yii2-cronjobs

安装:

composer  --prefer-dist denisogr/yii2-cronjobs "dev-master"

2配置:

在console/config/bootstrap.php中加入配置:设置别名

<?php
Yii::setAlias('@runnerScript', dirname(dirname(dirname(__FILE__))) .'/yii');

在console/config/main.php中加入配置:设置cron的controller

'controllerMap' => [
       'cron' => [
           'class' => 'denisog\cronjobs\CronController'
       ],
   ],

加完配置后的main.php文件如下:

<?php
$params = array_merge(
    require(__DIR__ . '/../../common/config/params.php'),
    require(__DIR__ . '/../../common/config/params-local.php'),
    require(__DIR__ . '/params.php'),
    require(__DIR__ . '/params-local.php')
);

return [
    'id' => 'app-console',
    'basePath' => dirname(__DIR__),
    'bootstrap' => ['log'],
    'controllerNamespace' => 'console\controllers',
    'components' => [
        //'log' => [
        //    'targets' => [
        //        [
        //            'class' => 'yii\log\FileTarget',
        //            'levels' => ['error', 'warning'],
        //        ],
        //    ],
       // ],
  
  'errorHandler' => [
//           'maxSourceLines' => 30,

        ],
    ],

'controllerMap' => [
       'cron' => [
           'class' => 'denisog\cronjobs\CronController'
       ],
   ],
    'params' => $params,
];

3.配置log,这个是为了输出cron整体的log

\common\config\main.php组件中加入log的配置:也即是配置log categories:cron_controller_logs

'log' =>[
      # 追踪级别
      # 消息跟踪级别
      # 在开发的时候,通常希望看到每个日志消息来自哪里。这个是能够被实现的,通过配置 log 组件的 yii\log\Dispatcher::traceLevel 属性, 就像下面这样:
      'traceLevel' => YII_DEBUG ? 3 : 0,
      
      # 通过 yii\log\Logger 对象,日志消息被保存在一个数组里。为了这个数组的内存消耗, 当数组积累了一定数量的日志消息,日志对象每次都将刷新被记录的消息到 log targets 中。 你可以通过配置 log 组件的 yii\log\Dispatcher::flushInterval 属性来自定义数量
      'flushInterval' => 1,
      
            'targets' => [
                'file1' =>[
          //'levels' => ['warning'],

          'categories' => ['cron_controller_logs'],
                    'class' => 'yii\log\FileTarget',
          
          # 当 yii\log\Logger 对象刷新日志消息到 log targets 的时候,它们并 不能立即获取导出的消息。相反,消息导出仅仅在一个日志目标累积了一定数量的过滤消息的时候才会发生。你可以通过配置 个别的 log targets 的 yii\log\Target::exportInterval 属性来 自定义这个数量,就像下面这样:
                    'exportInterval' => 1,
          # 输出文件
          'logFile' => '@app/runtime/logs/cron_controller_logs.log',
          
          # 你可以通过配置 yii\log\Target::prefix 的属性来自定义格式,这个属性是一个PHP可调用体返回的自定义消息前缀
          'prefix' => function ($message) {
            
            return $message;
          },
          # 除了消息前缀以外,日志目标也可以追加一些上下文信息到每组日志消息中。 默认情况下,这些全局的PHP变量的值被包含在:$_GET, $_POST, $_FILES, $_COOKIE,$_SESSION 和 $_SERVER 中。 你可以通过配置 yii\log\Target::logVars 属性适应这个行为,这个属性是你想要通过日志目标包含的全局变量名称。 举个例子,下面的日志目标配置指明了只有 $_SERVER 变量的值将被追加到日志消息中。
          # 你可以将 logVars 配置成一个空数组来完全禁止上下文信息包含。或者假如你想要实现你自己提供上下文信息的方式, 你可以重写 yii\log\Target::getContextMessage() 方法。
           'logVars' => [],
                ],
        
            ],
        ],

创建log文件,设置写权限  :

touch console/runtime/logs/cron_controller_logs.log
chmod 777 console/runtime/logs/cron_controller_logs.log

4.我们就要开始添加我们的cron任务了

在console/config/params.php中加入配置:

'cronJobs' =>[
        'test/example1' => [
            'cron'      => '* * * * *', 
      'cron-stdout'=> '/tmp/ExampleCommand.log',
      'cron-stderr'=> '/tmp/ExampleCommandError.log',			
        ],
    'test/example2' => [
            'cron'      => '10 * * * *',            
        ],

    ],

更详细的规则参看:

https://github.com/Yiivgeny/Yii-PHPDocCrontab/blob/master/examples/ExampleRuCommand.php

 

也就是加入了两个cron任务,一个是test/example1 这个任务是每分钟执行一次,这个任务的输出文件为cron-stdout对应的log文件,错误输出文件为cron-stderr对应的log文件。  另外一个任务是  test/example2

下面我们需要创建log文件:

touch  /tmp/ExampleCommand.log
touch /tmp/ExampleCommandError.log
chmod 777  /tmp/ExampleCommand.log
chmod 777  /tmp/ExampleCommandError.log

然后创建controller:

<?php
namespace console\controllers;
use Yii;
use yii\console\Controller;

class TestController extends Controller
{

   public function actionExample11(){
                echo 1;exit;

        }



}

然后执行:

./yii cron/run
tail -f /tmp/ExampleCommandError.log

可以看到错误输出

Error: Unknown command "test/example1".

因为上面我们的controller的action是actionExample11  ,两个1,去掉一个,在执行

vim /tmp/ExampleCommand.log

 

可以看到文件内容如下:

1

由于这个插件默认是覆盖写,所以需要修改一下:

170行附近的代码:

$command =
           $this->interpreterPath.' '.
           $command.
           $concat . escapeshellarg($stdout).
           ' 2>'.(($stdout === $stderr)?'&1':escapeshellarg($stderr));
       if ($this->isWindowsOS()){

改成:

$command =
           $this->interpreterPath.' '.
           $command.
           $concat . escapeshellarg($stdout).
           ' 2>>'.(($stdout === $stderr)?'&1':escapeshellarg($stderr));
       if ($this->isWindowsOS()){

30行左右的代码:

public $updateLogFile = false;

改为:

public $updateLogFile = true;

 

这就就可以覆盖写了,修改后的CronController.php文件内容如下:

<?php
/**
 * User: Denis Porplenko <denis.porplenko@gmail.com>
 * Date: 16.07.14
 * Time: 14:51
 */
namespace denisog\cronjobs;
use Yii;
use yii\console\Controller;
use yii\console\Exception;
class CronController extends Controller {
    const CATEGORY_LOGS = 'cron_controller_logs';
    /**
     * @var string PHPDoc tag prefix for using by PHPDocCrontab extension.
     */
    public $tagPrefix = 'cron';
    /**
     * @var string PHP interpriter path (if empty, path will be checked automaticly)
     */
    public $interpreterPath = null;
    /**
     * @var string path to writing logs
     */
    public $logsDir = null;
    /**
     * Update or rewrite log file
     * False - rewrite True - update(add to end logs)
     * @var bool
     */
    public $updateLogFile = true;
    /**
     * Placeholders:
     *     %L - logsDir path
     *     %C - name of command
     *     %A - name of action
     *     %P - pid of runner-script (current)
     *     %D(string formatted as arg of date() function) - formatted date
     * @var string mask log file name
     */
    public $logFileName = '%L/%C.%A.log';
    /**
     * @var string Bootstrap script path (if empty, current command runner will be used)
     */
    public $bootstrapScript = null;
    /**
     * @var string Timestamp used as current datetime
     * @see http://php.net/manual/en/function.strtotime.php
     */
    public $timestamp = 'now';
    /**
     * @var string the name of the default action. Defaults to 'run'.
     */
    public $defaultAction = 'run';
    /**
     * Initialize empty config parameters.
     */
    public function init() {
        parent::init();
        //Checking PHP interpriter path
        if ($this->interpreterPath === null){
            if ($this->isWindowsOS()){
                //Windows OS
                $this->interpreterPath = 'php.exe';
            }
            else{
                //nix based OS
                $this->interpreterPath = '/usr/bin/env php';
            }
        }
        //Checking logs directory
        if ($this->logsDir === null){
            $this->logsDir = Yii::$app->getRuntimePath();
        }
        //Checking bootstrap script
        if ($this->bootstrapScript === null){
            $this->bootstrapScript = Yii::getAlias('@runnerScript');
        }
    }
    /**
     * Provides the command description.
     * @return string the command description.
     */
    public function getHelp() {
        $commandUsage = Yii::getAlias('@runnerScript').' '.$this->getName();
        return <<<RAW
Usage: {$commandUsage} <action>
Actions:
    view <tags> - Show active tasks, specified by tags.
    run <options> <tags> - Run suitable tasks, specified by tags (default action).
    help - Show this help.
Tags:
    [tag1] [tag2] [...] [tagN] - List of tags
Options:
    [--tagPrefix=value]
    [--interpreterPath=value]
    [--logsDir=value]
    [--logFileName=value]
    [--bootstrapScript=value]
    [--timestamp=value]
RAW;
    }
    /**
     * Transform string datetime expressions to array sets
     *
     * @param array $parameters
     * @return array
     */
    protected function transformDatePieces(array $parameters){
        $dimensions = array(
            array(0,59), //Minutes
            array(0,23), //Hours
            array(1,31), //Days
            array(1,12), //Months
            array(0,6),  //Weekdays
        );
        foreach ($parameters AS $n => &$repeat) {
            list($repeat, $every) = explode('\\', $repeat, 2) + array(false, 1);
            if ($repeat === '*') $repeat = range($dimensions[$n][0], $dimensions[$n][1]);
            else {
                $repeatPiece = array();
                foreach (explode(',', $repeat) as $piece) {
                    $piece = explode('-', $piece, 2);
                    if (count($piece) === 2) $repeatPiece = array_merge($repeatPiece, range($piece[0], $piece[1]));
                    else                     $repeatPiece[] = $piece[0];
                }
                $repeat = $repeatPiece;
            }
            if ($every > 1) foreach ($repeat AS $key => $piece){
                if ($piece%$every !== 0) unset($repeat[$key]);
            }
        }
        return $parameters;
    }
    /**
     * Parsing and filtering PHPDoc comments.
     *
     * @param string $comment Raw PHPDoc comment
     * @return array List of valid tags
     */
    protected function parseDocComment($comment){
        if (empty($comment)) return array();
        //Forming pattern based on $this->tagPrefix
        $pattern = '#^\s*\*\s+@('.$this->tagPrefix.'(-(\w+))?)\s*(.*?)\s*$#im';
        //Miss tags:
        //cron, cron-tags, cron-args, cron-strout, cron-stderr
        if (preg_match_all($pattern, $comment, $matches, PREG_SET_ORDER)){
            foreach ($matches AS $match) $return[$match[3]?$match[3]:0] = $match[4];
            if (isset($return[0])){
                $return['_raw'] = preg_split('#\s+#', $return[0], 5);
                $return[0] = $this->transformDatePieces($return['_raw']);
                //Getting tag list. If empty, string "default" will be used.
                $return['tags'] = isset($return['tags'])?preg_split('#\W+#', $return['tags']):array('default');
                return $return;
            }
        }
    }
    /**
     * OS-independent background command execution .
     *
     * @param string $command
     * @param string $stdout path to file for writing stdout
     * @param string $stderr path to file for writing stderr
     */
    protected function runCommandBackground($command, $stdout, $stderr){
        $concat = ($this->updateLogFile) ? ' >>' : ' >';
        $command =
            $this->interpreterPath.' '.
            $command.
            $concat . escapeshellarg($stdout).
            ' 2>>'.(($stdout === $stderr)?'&1':escapeshellarg($stderr));
        if ($this->isWindowsOS()){
            //Windows OS
            pclose(popen('start /B "Yii run command" '.$command, 'r'));
        }
        else{
            //nix based OS
            system($command.' &');
        }
    }
    /**
     * Checking is windows family OS
     *
     * @return boolean return true if script running under windows OS
     */
    protected function isWindowsOS(){
        return strncmp(PHP_OS, 'WIN', 3) === 0;
    }
    /**
     * Running actions associated with {@link PHPDocCrontab} runner and matched with timestamp.
     *
     * @param array $args List of run-tags to running actions (if empty, only "default" run-tag will be runned).
     */
    public function actionRun($args = array()){
        $tags = &$args;
        $tags[] = 'default';
        //Getting timestamp will be used as current
        $time = strtotime($this->timestamp);
        if ($time === false) throw new CException('Bad timestamp format');
        $now = explode(' ', date('i G j n w', $time));
        $runned = 0;
        foreach ($this->prepareActions() as $task) {
            if (array_intersect($tags, $task['docs']['tags'])){
                foreach ($now AS $key => $piece){
                    //Checking current datetime on timestamp piece array.
                    if (!in_array($piece, $task['docs'][0][$key])) continue 2;
                }
                //Forming command to run
                $command = $this->bootstrapScript.' '.$task['command'].'/'.$task['action'];
                if (isset($task['docs']['args'])) $command .= ' '.escapeshellcmd($task['docs']['args']);
                //Setting default stdout & stderr
                if (isset($task['docs']['stdout'])) $stdout = $task['docs']['stdout'];
                else                                $stdout = $this->logFileName;
                $stdout = $this->formatFileName($stdout, $task);
                if(!is_writable($stdout)) {
                    $stdout = '/dev/null';
                }
                $stderr = isset($task['docs']['stderr'])?$this->formatFileName($task['docs']['stderr'], $task):$stdout;
                if(!is_writable($stderr)) {
                    $stdout = '/dev/null';
                }
                $this->runCommandBackground($command, $stdout, $stderr);
                Yii::info('Running task ['.(++$runned).']: '.$task['command'].' '.$task['action'], self::CATEGORY_LOGS);
            }
        }
        if ($runned > 0){
            Yii::info('Runned '.$runned.' task(s) at '.date('r', $time), self::CATEGORY_LOGS);
        } else {
            Yii::info('No task on '.date('r', $time), self::CATEGORY_LOGS);
        }
    }
    /**
     * Show actions associated with {@link PHPDocCrontab} runner.
     *
     * @param $args array List of run-tags for filtering action list (if empty, show all).
     */
    public function actionView($args = array()){
        $tags = &$args;
        foreach ($this->prepareActions() as $task) {
            if (!$tags || array_intersect($tags, $task['docs']['tags'])){
                //Forming to using with printf function
                $times = $task['docs']['_raw'];
                array_unshift($times, $task['command'].'.'.$task['action']);
                array_unshift($times, "Action %-40s on %6s %6s %6s %6s %6s %s\n");
                array_push($times, empty($task['docs']['tags'])?'':(' ('.implode(', ', $task['docs']['tags']).')'));
                call_user_func_array('printf', $times);
            }
        }
    }
    protected function formatFileName($pattern, $task){
        $pattern = str_replace(
            array('%L', '%C', '%A', '%P'),
            array($this->logsDir, $task['command'], $task['action'], getmypid()),
            $pattern
        );
        return preg_replace_callback('#%D\((.+)\)#U', create_function('$str', 'return date($str[1]);'), $pattern);
    }
    /**
     * Help command. Show command usage.
     */
    public function actionHelp(){
        echo $this->getHelp();
    }
    /**
     * Getting tasklist.
     *
     * @return array List of command actions associated with {@link PHPDocCrontab} runner.
     */
    protected function prepareActions()
    {
        $actions = array();
        try {
            $methods = Yii::$app->params['cronJobs'];
        }catch (yii\base\ErrorException $e) {
            throw new yii\base\ErrorException('Empty param cronJobs in params. ',8);
        }
        if (!empty($methods)) {
            foreach ($methods as $runCommand => $runSettings) {
                $runCommand = explode('/', $runCommand);
                if (count($runCommand) == 2) {
                    $actions[] = array(
                        'command' => $runCommand[0],
                        'action'  => $runCommand[1],
                        'docs'    => $this->parseDocComment($this->arrayToDocComment($runSettings))
                    );
                }
                if (count($runCommand) == 3) {
                    $actions[] = array(
                        'command' => $runCommand[0] . '/' . $runCommand[1],
                        'action'  => $runCommand[2],
                        'docs'    => $this->parseDocComment($this->arrayToDocComment($runSettings))
                    );
                }
                if (count($runCommand) == 4) {
                    $actions[] = array(
                        'command' => $runCommand[0] . '/' . $runCommand[1] .  '/' . $runCommand[2],
                        'action'  => $runCommand[3],
                        'docs'    => $this->parseDocComment($this->arrayToDocComment($runSettings))
                    );
                }
                if(empty($actions)) {
                    continue;
                }
            }
        }
        return $actions;
    }
    protected function arrayToDocComment(array $runSettings)
    {
        $result = "/**\n";
        foreach ($runSettings as $key => $setting) {
            $result .= '* @' . $key . ' ' . $setting . "\n";
        }
        $result .= "*/\n";
        return $result;
    }
}

然后我们需要设置cron ,让 ./yii cron/run 可以每分钟运行。

在console/config/params.php文件配置cron

每隔2分钟运行一次

'cron'      => '*\2 * * * *',

分:每1025,26,27,28,29,3040分钟

时:每天第2(偶数)小时

天:15至21和从23至27的数

月:一月至六月,偶数月

周:不管第几周

'cron' => '10,25-30,40 *\2 15-21,23-27 1-6\2 *',

 

 

yii2 数据库版本控制 migrations

对于在日常的开发,我们用mysql,当线下分几个小组对一个网站进行开发的时候,在线下建立表,如何同步到线上就成了一个问题。

svn git等版本控制系统,可以控制文件的版本,yii2通过migrate,来进行sql文件的上传,以及执行。

migrate的使用:

1.首先创建migrate文件,语法很简单:

./yii migrate/create    test_user

执行后,就会在/console/migrations/下面生成m160511_080937_test_user.php。

下面是执行log

[root@services datacenter_1000]# ./yii migrate/create    test_user  
Yii Migration Tool (based on Yii v2.0.7)

Create new migration '/www/web/datacenter/datacenter_1000/console/migrations/m160511_080937_test_user.php'? (yes|no) [no]:yes
New migration created successfully.

默认的路径是在console/migrations下面。

如果执行 ./yii migrate   就会执行console/migrations 下面的sql,当然,在执行前,需要到表migration 中查看信息,然后在决定执行那些sql,执行的是文件中的up()方法里面的sql,如果是事务操作,则不适用up(),而使用safeUp()方法。

有时候我们有自己的扩展,然后把migration放到自己的扩展里面,可以通过加入参数–migrationPath=@yii/log/migrations

来创建,执行相应的数据库脚本、

./yii migrate --migrationPath=@yii/log/migrations/

 

同样 down() , 采用 safeDown()方法。

如下是所有这些数据库访问方法的列表:

yii\db\Migration::execute(): 执行一条 SQL 语句
yii\db\Migration::insert(): 插入单行数据
yii\db\Migration::batchInsert(): 插入多行数据
yii\db\Migration::update(): 更新数据
yii\db\Migration::delete(): 删除数据
yii\db\Migration::createTable(): 创建表
yii\db\Migration::renameTable(): 重命名表名
yii\db\Migration::dropTable(): 删除一张表
yii\db\Migration::truncateTable(): 清空表中的所有数据
yii\db\Migration::addColumn(): 加一个字段
yii\db\Migration::renameColumn(): 重命名字段名称
yii\db\Migration::dropColumn(): 删除一个字段
yii\db\Migration::alterColumn(): 修改字段
yii\db\Migration::addPrimaryKey(): 添加一个主键
yii\db\Migration::dropPrimaryKey(): 删除一个主键
yii\db\Migration::addForeignKey(): 添加一个外键
yii\db\Migration::dropForeignKey(): 删除一个外键
yii\db\Migration::createIndex(): 创建一个索引
yii\db\Migration::dropIndex(): 删除一个索引

有时,你可能只需要提交一个或者少数的几个迁移,你可以使用该命令指定需要执行的条数,而不是执行所有的可用迁移。例如,如下命令将会尝试提交前三个可用的迁移:

yii migrate 3

你也可以指定一个特定的迁移,按照如下格式使用 migrate/to 命令来指定数据库应该提交哪一个迁移:

yii migrate/to 150101_185401                      # using timestamp to specify the migration 使用时间戳来指定迁移
yii migrate/to "2015-01-01 18:54:01"              # using a string that can be parsed by strtotime() 使用一个可以被 strtotime() 解析的字符串
yii migrate/to m150101_185401_create_news_table   # using full name 使用全名
yii migrate/to 1392853618                         # using UNIX timestamp 使用 UNIX 时间戳

 

其他:来自于官网资料。

还原迁移

你可以使用如下命令来还原其中一个或多个意见被提交过的迁移:

yii migrate/down     # revert the most recently applied migration 还原最近一次提交的迁移
yii migrate/down 3   # revert the most 3 recently applied migrations 还原最近三次提交的迁移

 

重做迁移

重做迁移的意思是先还原指定的迁移,然后再次提交。如下所示:

yii migrate/redo        # redo the last applied migration 重做最近一次提交的迁移
yii migrate/redo 3      # redo the last 3 applied migrations 重做最近三次提交的迁移

 

列出迁移

你可以使用如下命令列出那些提交了的或者是还未提交的迁移:

yii migrate/history     # 显示最近10次提交的迁移
yii migrate/history 5   # 显示最近5次提交的迁移
yii migrate/history all # 显示所有已经提交过的迁移

yii migrate/new         # 显示前10个还未提交的迁移
yii migrate/new 5       # 显示前5个还未提交的迁移
yii migrate/new all     # 显示所有还未提交的迁移

 

修改迁移历史

有时候你也许需要简单的标记一下你的数据库已经升级到一个特定的迁移,而不是实际提交或者是还原迁移。这个经常会发生在你手动的改变数据库的一个特定状态,而又不想相应的迁移被重复提交。那么你可以使用如下命令来达到目的:

yii migrate/mark 150101_185401                      # 使用时间戳来指定迁移
yii migrate/mark "2015-01-01 18:54:01"              # 使用一个可以被 strtotime() 解析的字符串
yii migrate/mark m150101_185401_create_news_table   # 使用全名
yii migrate/mark 1392853618                         # 使用 UNIX 时间戳

 

 

Yii2 – 如何写一个插件 , 如何做一个扩展

1.在github申请账户,申请完成就可以创建项目了

2.创建一个composer.json文件:内容如下:

{  
  "name": "zqy234/terrytest",  
  "description": "Yii2 zqy234 terrytest",  
  "keywords": [  
    "yii2",  
    "terrytest"  
  ],  
  "homepage": "https://github.com/zqy234/terrytest",  
  "type": "yii2-extension",  
  "license": "MIT",  
  "support": {  
    "source": "https://github.com/zqy234/terrytest"  
  },  
  "authors": [  
    {  
      "name": "terry water",  
      "email": "zqy234@126.com"  
    }  
  ],  
  "minimum-stability": "stable",  
  "require": {  
    "php": ">=5.4.0",  
    "yiisoft/yii2": ">=2.0.6"  
     
  },  
  "autoload": {  
    "psr-4": {  
      "terry\\": ""  
    }  
  },  
  "config": {  
    "process-timeout": 1800  
  }  
}

我的扩展的地址为:https://github.com/zqy234/terrytest

上面的信息大致核对自己的地址填写,比较重要的为下面

2.1.require 这个是当前插件需要的库包

2.2.autoload:为在aotuload自动加载的信息,psr-4:所在部分加入自动加载的信息,当访问terry\开头的,都会到这个插件的路径下面找文件。

在插件安装的时候,上面psr-4的信息会安装到文件/vendor/composer/autoload_psr4.php 中,具体信息,可以到这个文件夹中查看详细。

3.搞完上面的,我们需要在包管理里面添加。

打开:https://packagist.org/packages/submit

把github的地址提交,提交成功后的界面:

4.到这里就提交成功了,您可以使用composer下载了:

[root@iZ942k2d5ezZ cc]# composer require  zqy234/terrytest:dev-master  
./composer.json has been updated  
Loading composer repositories with package information  
Updating dependencies (including require-dev)  
  - Installing zqy234/terrytest (dev-master c80914f)  
    Cloning c80914fc7dedc2f464f16fb0af5d3a843326bddb  
  
Writing lock file  
Generating autoload files

5.使用正式版本号(稳定版本号)

首先去github中:

然后再packagist中更新:

然后就可以更新了:

[root@iZ942k2d5ezZ cc]# composer require --prefer-dist zqy234/terrytest  
Using version ^1.0 for zqy234/terrytest  
./composer.json has been created  
Loading composer repositories with package information  
Updating dependencies (including require-dev)  
  - Installing yiisoft/yii2-composer (2.0.3)                 
    Loading from cache  
  
  - Installing bower-asset/jquery (2.1.4)  
    Loading from cache  
  
  - Installing bower-asset/yii2-pjax (v2.0.5)  
    Loading from cache  
  
  - Installing bower-asset/punycode (v1.3.2)  
    Loading from cache  
  
  - Installing bower-asset/jquery.inputmask (3.1.63)  
    Loading from cache  
  
  - Installing cebe/markdown (1.1.0)  
    Loading from cache  
  
  - Installing ezyang/htmlpurifier (v4.6.0)  
    Loading from cache  
  
  - Installing yiisoft/yii2 (2.0.6)  
    Loading from cache  
  
  - Installing zqy234/terrytest (1.0.0)  
    Downloading: 100%           
  
Writing lock file  
Generating autoload files  
[root@iZ942k2d5ezZ cc]#

这样就安装成功了。

6.版本号自动更新(github和packagist之间)

访问:https://packagist.org/profile/

获取api token

详细说明地址:https://packagist.org/about

填写的url的格式:https://packagist.org/api/bitbucket?username=USERNAME&apiToken=API_TOKEN

可以用:

curl -XPOST -H'content-type:application/json' 'https://packagist.org/api/update-package?username=USERNAME&apiToken=API_TOKEN' -d'{"repository":{"url":"PACKAGIST_PACKAGE_URL"}}'

来检测。

测试:

在github那边增加一个稳定版本号

然后再 https://packagist.org/packages/zqy234/terrytest#1.0.1

发现:1.0.1出来了。

基本的详细大致使用就这些

总之,可以快乐的,让别人下载自己的库包了

composer require --prefer-dist zqy234/terrytest

 

Yii2 安装 – composer

yii2的框架的安装,以及扩展的安装,都是用composer,下面是安装composer的具体方法:

  1. 查看php是否存在
    php --version

    如果不存在,将安装的php设置下快捷方式,这样,就可以直接用php执行。

  2. 我当前的php的安装路径为/usr/local/php/bin/php 我设置了下快捷方式:
  3. ln -s /usr/local/php/bin/php  /usr/local/bin/php
  4. 安装composer
    curl -sS https://getcomposer.org/installer | php
    mv composer.phar /usr/local/bin/composer
  5. 到这里就安装完成了,譬如,我可以用下面的方式安装asset
    composer global require "fxp/composer-asset-plugin:~1.1.1"

    上面如果报错:参看:http://stackoverflow.com/questions/36579559/updating-composer-throws-exception-class-fxp-composer-assetplugin-repository-npm

    try to update your fxp/composer-asset-plugin:

    php composer.phar global update fxp/composer-asset-plugin --no-plugins

    PS: If after update it still not working, try also following:

    composer global require fxp/composer-asset-plugin --no-plugins

    如果上面的方法还是有问题,譬如我曾经的报错:

    我采取的办法是清空composer global存放的文件夹。我的composer我使用的是root账户,我清空命令如下:

    rm -rf /root/.composer

    然后重新安装:

    composer global require "fxp/composer-asset-plugin:^1.2.0"

    文章参考:

    https://getcomposer.org/doc/03-cli.md#global

https://getcomposer.org/doc/03-cli.md#composer-home