yii2 使用apidoc生成技术文档

yii2 的apidoc生成文档还是蛮好用的。
首先说一下这个玩意是干什么的,就是用来生成技术文档,效果可以查看:http://www.fecshop.com/doc/fecshop-guide/cn-1.0/guide-index.html#

1.需要安装yii2,参看文章:

Yii2 – 安装高级模板 advanced project template

2.然后安装doc 插件

2.1在安装的yii2根目录下面执行:

composer require --prefer-dist yiisoft/yii2-apidoc

2.2或者在composer.json中添加:

"yiisoft/yii2-apidoc": "~2.1.0"

然后执行:composer update

可能会报错,如果报错,将上面的改成:

yiisoft/yii2-apidoc": "~2.0.0"

安装成功就可以使用了

3.写md文件

可以看下面的第四部的路径,md文件存放的地址: /www/web/develop/fecshop/doc/fecshop-guide/cn-1.0,那么我们在这个路径下面新建2个比较重要的md文件

README.md 和 index.md

README.md 是左侧菜单部分

index.md 是guide-index.html页面,一般和README.md一样,用来做首页菜单索引,当然也可以添加自己想要的内容

生成的效果可以参看:

http://www.fecshop.com/doc/fecshop-guide/cn-1.0/guide-index.html#

展示一个demo:

README.md:

FecShop 文档说明
===============================

版权所有。

2016 (c) FecShop Software LLC。

目前进度
-----
*  [Fecshop 开发进度](fecshop-jindu.md)
*  [Fecshop 开发LOG](fecshop-jindu-log.md)

关于与安装
-----

*  [Fecshop 关于-架构特点](fecshop-about-fecshop.md)
*  [Fecshop 关于-功能介绍](fecshop-about-function.md)
*  [Fecshop 关于-系统结构](fecshop-about-system-contruct.md)
*  [Fecshop 安装与初始配置](fecshop-install.md)

Fecshop配置
-----------

*  [Fecshop 配置-配置结构](fecshop-init-config-construction.md)
*  [Fecshop 配置-Yii2配置](fecshop-init-config-yii2.md)
*  [Fecshop 配置-fecshop配置](fecshop-init-config-advanced.md)

index.md

FecShop 文档说明
===============================

版权所有。

2016 (c) FecShop Software LLC。


关于与安装
-----

*  [Fecshop 关于-架构特点](fecshop-about-fecshop.md)
*  [Fecshop 关于-功能介绍](fecshop-about-function.md)
*  [Fecshop 关于-系统结构](fecshop-about-system-contruct.md)
*  [Fecshop 安装-composer](fecshop-install.md)
*  [Fecshop 安装-初始配置](fecshop-init-config.md)



功能介绍
--------
*  [Fecshop 功能概叙](fecshop-feature-summary.md)
*  [Fecshop Url自定义](fecshop-feature-url-custom.md)
*  [Fecshop 多语言](fecshop-feature-mutil-languages.md)
*  [Fecshop 多store](fecshop-feature-mutil-stores.md)
*  [Fecshop 多货币](fecshop-feature-mutil-currency.md)
*  [Fecshop 多模板](fecshop-feature-mutil-themes.md)
*  [Fecshop 多入口](fecshop-feature-mutil-entrances.md)
*  [Fecshop 配置层](fecshop-feature-config.md)
*  [Fecshop 模块层](fecshop-feature-modules.md)
*  [Fecshop Block层](fecshop-feature-block.md)
*  [Fecshop 服务层](fecshop-feature-component-services.md)
*  [Fecshop 独立功能块](fecshop-feature-independent-block.md)
*  [Fecshop 权限管理](fecshop-feature-role.md)
*  [Fecshop 缓存管理](fecshop-feature-cache.md)
*  [Fecshop 脚本管理](fecshop-feature-script.md)
*  [Fecshop 后台日志](fecshop-feature-admin-log.md)
*  [Fecshop 重写功能](fecshop-feature-rewrite.md)
*  [Fecshop 升级](fecshop-feature-upgrade.md)

在README.md文件里面你可以看到,在括号里面的文件  ,譬如 fecshop-jindu.md

你需要在 /www/web/develop/fecshop/doc/fecshop-guide/cn-1.0下新建括号里面的md文件,然后在里面写内容就行了,譬如:

关于 Fecshop 的框架特点
=======================

Fecshop 全称为Fancy ECommerce Shop,是一款优秀的开源电商系统,遵循[OSL3.0协议](http://www.oschina.net/question/28_8527),
目的是为了方便yii2用户快速的
开发商城,Fecshop作为一款可以持续性发展的商城系统,
在框架层面有以下特性:

1. 由于商城系统的复杂性,原始的框架MVC结构,显的有点力不从心,Fecshop框架
加入了[Block层](fecshop-feature-block.md),
Controller层只负责调度, Model只负责数据库映射,中间的处理逻辑由block来完成,View层
负责显示,这样各司其职, 以免造成controller文件过于庞大。

2. 加入[独立功能块](fecshop-feature-independent-block.md),有点类似Yii2的Widget,目的是为了让一些侧栏公用块
可以通过配置的方式
添加,同时,还可以具有设置缓存的功能,譬如侧栏的产品浏览记录,
newsletter等独立显示块可能在很多
页面用到,通过独立功能块可以配置方便的载入。

 

 

4. 写个sh脚本用来生成文档,当然,您可以可以直接运行里面的代码,不过用sh运行比较方便:

#!/bin/sh

rm -rf /www/web/online-2/www.fecshop.com/appfront/web/doc/fecshop-guide/cn-1.0/*
/www/web/develop/fecshopdoc/vendor/bin/apidoc  guide   /www/web/develop/fecshop/doc/fecshop-guide/cn-1.0   /www/web/online-2/www.fecshop.com/appfront/web/doc/fecshop-guide/cn-1.0/  --interactive=0

md文件存放的地址为: /www/web/develop/fecshop/doc/fecshop-guide/cn-1.0

md生成的html的地址为:/www/web/online-2/www.fecshop.com/appfront/web/doc/fecshop-guide/cn-1.0/

 

然后nginx 指向/www/web/online-2/www.fecshop.com/appfront/web

然后访问http://127.0.0.1/doc/fecshop-guide/cn-1.0/guide-index.html

就可以看到效果了

效果演示:http://www.fecshop.com/doc/fecshop-guide/cn-1.0/guide-index.html#

 

最后,您可能感觉样式不好,想改一下,可以修改;

vendor/bower/bootstrap/dist/css/bootstrap.css

或者添加文件,覆盖这个文件里面的css内容即可。

如果您要大改,把页面底部彻底换掉,可以到

/www/web/tadoc/vendor/yiisoft/yii2-apidoc/templates

这里找对应的代码,然后改掉html内容,在添加css内容等等

 

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

 

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 时间戳