Yii2.0基础框架

时间:2022-10-20 20:37:23

前言:最近在用php写一个项目的接口,所以需要学习一下Yii的框架,也在这里记录一下。

整体结构

Yii2.0基础框架

ssets文件夹:assets的作用是方便模块化,插件化的,一般来说出于安全原因不允许通过url访问protected下面的文件 ,但是我们又希望将module单独出来,所以需要使用发布,即将一个目录下的文件复制一份到assets下面方便通过url访问。

commands文件夹:控制台脚本存放的地方,自动运行脚本

config文件夹:配置文件存放的文件夹

controller文件夹:MVC中C文件存放的文件夹

mail文件夹:邮件发送目录,具体干啥的我还在摸索中哈~

models文件夹:MVC中M文件存放的文件夹

runtime:日志文件

tests:测试脚本文件夹

vendor:第三方组件存放,composer下载的组件存放的文件夹,自动帮你autoload

views:MVC中V存放的文件夹

web:web主应用入口脚本存放的位置

以上是整个文件夹的布局,可以根据自己的项目灵活变化,我们公司的项目中就弱化了MVC里面的V,把V放在了前端。

官方文档【中文版】:http://www.yiichina.com/doc/guide/2.0

Yii 应用参照模型-视图-控制器 (MVC)设计模式来组织。当时听到MVC也是一脸懵逼,组长给我几个网址就让我自己去了解了。

M模型代表数据、业务逻辑和规则;V视图展示模型的输出;C控制器接受出入并将其转换为模型和视图命令。

Yii2.0基础框架

 

这是官网上面的框架结构设计,MVC就是其中的控制器,视图和模型,他们的各自作用上面也讲了下,一般的后端应用,M表示从数据库、第三方链接、本地文件中获取的数据进行处理,整理,在交给到V端,V端的作用一般是在页面中反馈给用户的页面,如果是以数据的形式返回给用户,那这个V层就不用做过多的渲染。C层的话主要是连接两者的作用,C层获取到用户的请求,传给M层,M层处理好数据,反馈给C层,C层再将数据给到V层,V层展示给用户。MVC模型的便捷之处就是逻辑清晰,每个模块负责自己的事,有条有理,非常便于初学者理解,是一个入门的模型。

除此之外,Yii还包含其他逻辑处理块,比方说上面图中的入口脚本【调用应用一开始必被调用的脚本文件】,应用主体【Yii::$app全局可访问对象】,应用组件【全局通用的一些工具集】,模块【业务逻辑单元,每个业务逻辑一个模块,会让代码很清晰】,过滤器【规范行为的对象,在控制器执行之前或之后调用,定义一类特殊的行为】,前端资源和小部件我们先不讲,因为是涉及到前端的一些组件内容,后面我会单独开辟一个系列来讲前端知识,我出这一系列的目的主要是针对后台应用~

入口脚本

web文件夹下面的index.php就是入口脚本,每次web请求都必须经过它!

Yii2.0基础框架

还有个入口脚本是啥呢,控制台脚本,下面的那个叫yii的php脚本,啥作用呢,你们想想啊,电商后台中,如果有很多人要调整库存,是不是调整一次就给改一次呀,肯定不会呀,库存操作如果调用数据库太频繁了,数据库肯定扛不住的,我们的做法就是先放到类似于Redis的缓存中,等到一定量的时候,或者有个1秒钟的时候我们给同步一次数据库,同步的方式就是调用控制台脚本啦,配合Linux的crontab,完美解决数据库调用过于频繁的问题。控制台脚本后面我们会介绍,一般业务线中用的还挺多的。

入口脚本主要完成以下工作:

  • 定义全局常量;
  • 注册 Composer 自动加载器;
  • 包含 Yii 类文件;
  • 加载应用配置;
  • 创建一个应用实例并配置;
  • 调用 yii\base\Application::run() 来处理请求。

 

<?php

// comment out the following two lines when deployed to production
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');

require(__DIR__ . '/../vendor/autoload.php');
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');

$config = require(__DIR__ . '/../config/web.php');

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

入口脚本是定义全局常量的最好地方,话虽如此,不建议在这里定义啥全局变量!Yii 支持以下三个常量:

YII_DEBUG:标识应用是否运行在调试模式。当在调试模式下,应用会保留更多日志信息,如果抛出异常,会显示详细的错误调用堆栈。因此,调试模式主要适合在开发阶段使用,YII_DEBUG 默认值为 false。

YII_ENV:标识应用运行的环境。YII_ENV 默认值为 'prod',表示应用运行在线上产品环境。

YII_ENABLE_ERROR_HANDLER:标识是否启用 Yii 提供的错误处理,默认为 true。

autoload.php:自动加载器,这个是注册composer自动加载器的。

Yii.php,包含Yii类的文件路径。

应用主体配置

应用主体在入口脚本中创建并能通过表达式 \Yii::$app 全局范围内访问。访问的变量定义在哪儿呢,由于应用主体配置比较复杂,就是刚刚提到的$config,config文件夹中的web.php文件。后面比较复杂的配置都可以放到单个文件中,这是个技巧,即减少了配置文件的代码行数,也将整个框架清晰很多。

Yii2.0基础框架

这里定义了很多属性我们来分别看一下

<?php

$params = require(__DIR__ . '/params.php');

$config = [

params这个参数里的所有变量就被定义在params.php这个文件夹里面

下面是我的项目中配置的一些文件在上方定义

<?php

$params = require(__DIR__ . '/params.php');
$rules = require(__DIR__ . '/rules.php');
$aliases = require(__DIR__ . '/aliases.php');
$cacheConfig = require(__DIR__ . '/cache.php');

这里面主要是params参数,rules路由规则,aliases别名规则,cacheConfig缓存配置

为什么没有db,db是区分环境的,在index.php中会区分stable环境,pro环境还是测试环境

回到YII源码,配合文件上方配置了params所在的文件

$config = [
    'id' => 'basic',
    'basePath' => dirname(__DIR__),
    'bootstrap' => ['log'],
    'components' => [
        'request' => [
            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
            'cookieValidationKey' => 'A9BMCrvbxuCEnE39rVpOUECgcBJTnzUH',
        ],
        'cache' => [
            'class' => 'yii\caching\FileCache',
        ],
        'user' => [
            'identityClass' => 'app\models\User',
            'enableAutoLogin' => true,
        ],
        'errorHandler' => [
            'errorAction' => 'site/error',
        ],
        'mailer' => [
            'class' => 'yii\swiftmailer\Mailer',
            // send all mails to a file by default. You have to set
            // 'useFileTransport' to false and configure a transport
            // for the mailer to send real emails.
            'useFileTransport' => true,
        ],
        'log' => [
            'traceLevel' => YII_DEBUG ? 3 : 0,
            'targets' => [
                [
                    'class' => 'yii\log\FileTarget',
                    'levels' => ['error', 'warning'],
                ],
            ],
        ],
        'db' => require(__DIR__ . '/db.php'),
        /*
        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'rules' => [
            ],
        ],
        */
    ],
    'params' => $params,
];

这里面有几个比较重要的属性:id、basePath、bootstrap、components

其他还有几个比较重要的属性:aliases、language、moudules

id:

yii\base\Application::id 属性用来区分其他应用的唯一标识ID。一般配置为程序名称。必要属性之一

basePath:

yii\base\Application::basePath 指定该应用的根目录,系统预定义@app代表这个路径。如果需要require目录被的文件,可以使用这个方式找到对应文件。另外一个必要属性,这个是必须得配置的。

bootstrap:

这个属性很实用,他允许你用数组启动阶段yii\base\Application::bootstrap()需要运行的组件,一般后端应用中配置这个'bootstrap' => ['log']即可

component:

这是最重要的属性,他允许你注册多个在其他地方使用的应用组件,比方说session、log、db和cache,都在这里配置的

aliases

该属性允许你用一个数组定义多个别名。数组的key为别名名称,值为对应的路径。

[
    'aliases' => [
        '@name1' => 'path/to/path1',
        '@name2' => 'path/to/path2',
    ],
]

language

该属性指定应用展示给终端用户的语言,默认为 en 标识英文。如果需要之前其他语言可以配置该属性

  'language' => 'zh-CN',

modules

该属性指定应用所包含的模块。还记得上面说的业务的分割吗,是的,就是在modules里面进行划分的,对应的目录就是根目录下的modules目录,也需要配置

[
    'modules' => [
        // "booking" 模块以及对应的类
        'booking' => 'app\modules\booking\BookingModule',

        // "comment" 模块以及对应的配置数组
        'comment' => [
            'class' => 'app\modules\comment\CommentModule',
            'db' => 'db',
        ],
    ],
]

这个配置对你代码的结构清晰性有很大的提升,后端应用必配属性。

defaultRoute

该属性指定未配置的请求的响应 路由规则,路由规则可能包含模块ID,控制器ID,动作ID。

例如

helppost/createadmin/post/create,如果动作ID没有指定,会使用yii\base\Controller::defaultAction中指定的默认值。

对于 yii\web\Application 网页应用,默认值为 'site' 对应 SiteController 控制器,并使用默认的动作。

对于 yii\console\Application 控制台应用, 默认值为 'help' 对应 yii\console\controllers\HelpController::actionIndex()。

应用组件

 在同一个应用中,每个应用组件都有一个独一无二的 ID 用来区分其他应用组件,你可以通过如下表达式访问应用组件

\Yii::$app->componentID

官网给出的components示例如下

[
    'components' => [
        // 使用类名注册 "cache" 组件
        'cache' => 'yii\caching\ApcCache',

        // 使用配置数组注册 "db" 组件
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=demo',
            'username' => 'root',
            'password' => '',
        ],

        // 使用函数注册"search" 组件
        'search' => function () {
            return new app\components\SolrService;
        },
    ],
]

这里定义了三个配置属性:cache、db、search,除此之外,比较重要的components属性还有如下几个:urlManager、user、session、errorHandler和log。

cache

代表代表各种缓存存储器,例如内存,文件,数据库。

db

代表一个可以执行数据库操作的数据库连接。

search

搜索组件入口配置,以后有机会和大家唠唠solr相关,切割关键字,切割,切割~

urlManager

支持URL地址解析和创建。

user

代表认证登录用户信息,仅在yii\web\Application 网页应用中可用。如果有后台用户权限认证管理,可去官网了解明细。

session

代表会话信息,仅在yii\web\Application 网页应用中可用,我们一般都是通过这个来控制用户属性的,

errorHandler

处理 PHP 错误和异常

log

全局日志配置入口

请谨慎注册太多应用组件,应用组件就像全局变量,使用太多可能加大测试和维护的难度。 一般情况下可以在需要时再创建本地组件。

大家可以着重关注几个配置:db、urlManager、log和cache,这些都是在日常工作中用的比较多的,必配的配置。

控制器

MVC里面的C,下面我们称它为controller,因为他是入口,我入口脚本一样,用户发来请求,首先到的就是控制器(这里的先到只是在后台应用中先到,如果包含前端资源,那么肯定是先接触到前端代码的)

Controller的主要职责是接受用户请求,分发处理用户请求,拿到结果数据,递交给V层展示给客户

首先继承yii\base\Controller类的对象

Controller由操作【action】组成,他是执行终端用户请求的最基础的单元,一个控制器可以有多个或一个操作

如下示例显示包含两个操作view和create的控制器post:

Yii2.0基础框架Yii2.0基础框架
namespace app\controllers;

use Yii;
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;

class PostController extends Controller
{
    public function actionView($id)
    {
        $model = Post::findOne($id);
        if ($model === null) {
            throw new NotFoundHttpException;
        }

        return $this->render('view', [
            'model' => $model,
        ]);
    }

    public function actionCreate()
    {
        $model = new Post;

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('create', [
                'model' => $model,
            ]);
        }
    }
}
View Code

用户访问的时候就是直接被指向到某个action里面的

有这个action方法了,那我们如何访问它呢?这边就涉及到一个叫路由的东西

终端用户通过所谓的【路由】寻找到操作

路由使用如下格式

ControllerID/ActionID

模块下的控制器,使用如下格式:

ModuleID/ControllerID/ActionID

如果用户的请求地址为 http://hostname/index.php?r=site/index, 会执行site控制器的index操作。?后面的是参数,r参数就代表了访问的路径

那么如何去创建一个控制器呢?

在yii\web\Application网页应用中,控制器应继承yii\web\Controller或它的子类

在yii\console\Application控制台应用中,控制器应继承yii\console\Controller或它的子类

namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
}

如上代码

site就为这个Controller的控制器id,控制器id通常都是和资源有关的名词

注意:

Yii框架下都是通过射峰命名法去查找对应的ID的,比方说这边的SiteController,在URL调用中只需要保留site,后面的Controller不需要放到URL里面,控制器类必须能被自动加载

下面为一些实例,假设yii\base\Application::controllerNamespace控制器命名空间为app\controller:

 

  • article 对应 app\controllers\ArticleController;
  • post-comment 对应 app\controllers\PostCommentController;
  • admin/post-comment 对应 app\controllers\admin\PostCommentController;
  • adminPanels/post-comment 对应 app\controllers\adminPanels\PostCommentController

每个应用有一个由yii\base\Application::defaultRoute属性指定的默认控制器;就是我们上面讲的那个

 

[
    'defaultRoute' => 'main'
]

当请求没有指定的路由,该属性作为路由使用

对于yii\web\Application它的值为site

对于yii\console\Application它的值为help

所以URL为 http://hostname/index.php 表示由site控制器来处理

那么如何创建action呢

创建action可简单地在控制器类中定义所谓的操作方法来完成,操作方法必须是以action开头的公有方法。和controller一样,命名的时候是使用蛇峰命名法,访问的时候全部小写,如果中间有多个大写字母,用-间隔即可

namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
    public function actionIndex()
    {
        return $this->render('index');
    }

    public function actionHelloWorld()
    {
        return 'Hello World';
    }
}

action的参数值从请求中获取

对于yii\web\Application网页应用,每个操作参数的值从$_GET中获得,参数名作为键;一般不会使用$_GET直接去加参数,这样不安全。一般使用app的Request去取出用户请求的body个head

$request = Yii::$app->request;

对于yii\console\Application控制台应用, 操作参数对应命令行参数

如下例,操作view (内联操作) 申明了两个参数 $id 和 $version

namespace app\controllers;

use yii\web\Controller;

class PostController extends Controller
{
    public function actionView($id, $version = null)
    {
        // ...
    }
}

操作参数会被不同的参数填入,如下示例:

  • http://hostname/index.php?r=post/view&id=123$id 会填入'123'$version 仍为 null 空因为没有version请求参数
  • http://hostname/index.php?r=post/view&id=123&version=2: $id 和 $version 分别填入 '123' 和 '2'`
  • http://hostname/index.php?r=post/view: 会抛出yii\web\BadRequestHttpException 异常 因为请求没有提供参数给必须赋值参数$id
  • http://hostname/index.php?r=post/view&id[]=123: 会抛出yii\web\BadRequestHttpException 异常 因为$id 参数收到数字值 ['123']而不是字符串

 

模型

学习完了MVC的C,现在来学习MVC中最最最最最最重要的数据模型层,这一层负责数据的整合处理,包括业务逻辑控制,从数据库拿出数据,打包数据,缓存操作,返回给Controller等一系列的操作,可谓是日常业务逻辑工作中每天大部分时间都在敲的代码了。

模型可通过继承yii\base\Model 或它的子类定义,基类yii\base\Model支持许多实用的特性:

Model 类也是更多高级模型如Active Record 活动记录的基类,不得不说,对于数据库操作不熟练的朋友可以尝试一下Active Record,Yii的这个特性让你操作数据库像操作对象一样简单。

定义属性:也就是定义model里面的变量:

 

namespace app\models;

use yii\base\Model;

class ContactForm extends Model
{
    public $name;
    public $email;
    public $subject;
    public $body;
}

 

之前做过java,eclipse自动生成get,set方法,感觉好方便,这边,一般情况下不需要生成get,set方法,除非你的数据库操作是通过Active Record进行的,你才需要去覆盖魔术方法如__get()__set()使属性像普通对象属性被访问。

对于model里面属性的访问,这要感谢yii\base\Model支持 ArrayAccess 数组访问 和 ArrayIterator 数组迭代器。可以使用如下两种方式对属性进行访问,是public属性哟。

$model = new \app\models\ContactForm;

// "name" 是ContactForm模型的属性
$model->name = 'example';
echo $model->name;
$model = new \app\models\ContactForm;

// 像访问数组单元项一样访问属性
$model['name'] = 'example';
echo $model['name'];

// 迭代器遍历模型
foreach ($model as $name => $value) {
    echo "$name: $value\n";
}

属性标签,一般应用在输入错误的提示处,属性标签是视图的一部分,但是在模型中申明标签通常非常方便,并可形成非常简洁重用代码。不过一般在后台应用用很少涉及到。

属性的规范很重要,常规的操作是不允许两个model之间互相调用各自的属性的,如有必要,一般model中比较常规的属性放到全局变量中去控制,后台应用中,可供给model操作的数据均是来源于用户的,我们需要做的更多的是对用户输入的控制。你可能在生活中对这些用户输入的控制直接就使用了,千万不能这么干,有几部还是需要做的,为了安全性。其一css攻击的防范;其二用户输入的验证。

xss的攻击,Yii提供了相应的方式应对,如果这个参数不对数据库进行操作,一般情况下无需验证,如果需要对数据库进行验证,需要进行xss验证,具体的实例代码百度一下,一大堆,这里就不做讲解啦。

对于用户输入的验证,最好的做法是在Controller里面对用户的行为进行控制,也就是重写behaviors这个方法,然后对每个action进行验证。

'validation' => [
                'class' => 'ext\controller\behavior\Validation',
                'verifyPath' => 'app\modules\api\modules\v1\models\validation\\',
                'indexName' => 'body',
                'enabled' => true
],

其实,是否需要登录验证,是否需要缓存都可以在behaviors里面进行配置。

这样区分开来不但能够避免在model里面写过多校验代码,也可以使你的model层更加清晰简洁。

其实这个应该是在Controller那边说的,既然这边提到数据校验,就一笔带过啦~具体的如何操作,我们会在下面一节【过滤器】中详解。

对于数据的获取,数据库还是缓存,我们在接下来的章节中继续讲解,这边就不细究了,进入下一个环节,View。

对于Model,也就这么多,其他的和写一般的function一样,写业务逻辑就可以啦。

 

视图

后端接口中的应用,这个模块很少被使用到,如果大姐对这个模块感兴趣,可以去官网详查,我这边的方法很简单宝莉,直接return给用户数据即可。

不要忘了,前端需要什么样的数据格式,我们这边需要在最后return的时候encode一下哈。最常用的就是josn格式啦。

 

public function formatJson($data = [])
    {
        Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
        return $data;
    }

 

模块

你可能没有在Yii2.0的源码中找到关于模块的代码,那是因为Yii最大的优势是开发后台系统,并没有想过需要把业务逻辑切割的那么细,我们在日常开发后台接口的时候,会有很多独立的系统,比方说用户模块,订单模块,支付模块等等等等。那模块的优势就显示出来了,每个系统一个模块,清清楚楚。

如何创建模块呢

模块是独立的元件单元,由模型、视图、控制器、和其他支持组件组成,是不是感觉就是个小型的MVC模型,没错,每个模块都是一个独立的单元。

使用模块必须在应用主体中配置它,终端用户可以直接访问在应用主体中已安装的模块的控制器

如下例子显示一个模型的目录结构

app/
    Module.php                   模块类文件
    controllers/                 包含控制器类文件
        DefaultController.php    default 控制器类文件
    models/                      包含模型类文件
    views/                       包含控制器视图文件和布局文件

Module.php是基础模块类,继承yii\base\Module的模块类,该文件直接放在模块的yii\base\Module::basePath【模块对应的主目录】下,必须能被自动加载

在应用主体中配置模块,如下示例

$config = [
    'id' => 'app.name',
    'basePath' => dirname(__DIR__),
    'bootstrap' => ['log'],
    'language' => 'zh-CN',
    'components' => [
       ...
    ],
    'params' => $params,
    'modules' => [
        'user' => [
            'class' => 'app\modules\user\User',
        ],
        'goods' => [
            'class' => 'app\modules\goods\Goods',
        ],
        'store' => [
            'class' => 'app\modules\store\Store',
        ],
        'supply' => [
            'class' => 'app\modules\supply\Supply',
        ],
        'order' => [
            'class' => 'app\modules\order\Order',
        ],
        'stock' => [
            'class' => 'app\modules\stock\Stock',
        ],
        'pay' => [
            'class' => 'app\modules\pay\Pay',
        ],
        'message' => [
            'class' => 'app\modules\message\Message',
        ],
    ],
    'aliases' => $aliases,
    'defaultRoute' => 'admin'
];

主体应用中配置的模块就指向刚刚建立的模块基础类。

每个模块可以有自己独立的配置文件,将模块中需要用到的共同的参数放到这个配置文件中,并在模块基础类中添加进去。

下面就是我的用户模块的目录和代码

Yii2.0基础框架

<?php
namespace app\modules\goods;

use Yii;

class Goods extends \yii\base\Module
{
    public $controllerNamespace = 'app\modules\goods\controllers';

    public function init()
    {
        parent::init();

        Yii::configure($this, require(__DIR__ . '/config.php'));
    }
}

访问路由

和访问应用的控制器类似,路由也用在模块中控制器的寻址, 模块中控制器的路由必须以模块ID开始,接下来为控制器ID和操作ID。 例如,假定应用使用一个名为 forum 模块,路由forum/post/index 代表模块中 post 控制器的 index 操作, 如果路由只包含模块ID,默认为 default 的yii\base\Module::defaultRoute 属性来决定使用哪个控制器/操作, 也就是说路由 forum 可能代表 forum 模块的 default 控制器。

一般访问模块的方式如下:

// 获取ID为 "forum" 的模块
$module = \Yii::$app->getModule('forum');

// 获取处理当前请求控制器所属的模块
$module = \Yii::$app->controller->module;

模块在大型项目中常被使用,这些项目的特性可分组,每个组包含一些强相关的特性, 每个特性组可以做成一个模块由特定的开发人员和开发组来开发和维护。

在特性组上,使用模块也是重用代码的好方式,一些常用特性,如用户管理,评论管理,可以开发成模块, 这样在相关项目中非常容易被重用。无耻的抄袭了官网文档的最佳实践,其实个人感觉官网文档的最佳实践是最给力的内容。

过滤器

在模块那一节,我们提到一个校验的神器,behavior,过滤器是Controller动作执行之前或之后执行的对象,所以我说应该放在Controller一节去讲,但是在model层数据处理中提到了,就在那边一笔代了下,现在来详细的说说这个吧。

过滤器可包含 预过滤(过滤逻辑在动作之前) 或 后过滤(过滤逻辑在动作之后),也可同时包含两者。

我们上面仅是提及了过滤器的一个属性,validation,这也是我自定义的一个属性,用于校验用户输入的正确性。

完整的官网对behaviors的定义如下:

public function behaviors()
{
    return [
        [
            'class' => 'yii\filters\HttpCache',
            'only' => ['index', 'view'],
            'lastModified' => function ($action, $params) {
                $q = new \yii\db\Query();
                return $q->from('user')->max('updated_at');
            },
        ],
    ];
}

控制器类的过滤器默认应用到该类的所有action,你可以在only参数中指定应用到哪几个action。也可以配置yii\base\ActionFilter::except属性使一些动作不执行过滤器。

一般情况下,我们使用预过滤,很少会被用到后过滤,他们有啥区别呢。

预过滤

  • 按顺序执行应用主体中behaviors()列出的过滤器。
  • 按顺序执行模块中behaviors()列出的过滤器。
  • 按顺序执行控制器中behaviors()列出的过滤器。
  • 如果任意过滤器终止动作执行,后面的过滤器(包括预过滤和后过滤)不再执行。
  • 成功通过预过滤后执行动作。

后过滤

  • 倒序执行控制器中behaviors()列出的过滤器。
  • 倒序执行模块中behaviors()列出的过滤器。
  • 倒序执行应用主体中behaviors()列出的过滤器。

创建过滤器,继承 yii\base\ActionFilter 类并覆盖 yii\base\ActionFilter::beforeAction() 和/或 yii\base\ActionFilter::afterAction() 方法来创建动作的过滤器,前者在动作执行之前执行,后者在动作执行之后执行。

下面是我在项目中使用的公共验证器。

<?php
namespace ext\controller\behavior;

use Yii;
use yii\base\ActionFilter;
use yii\base\Exception;

class Validation extends ActionFilter
{
    public $enabled = true; //默认打开验证

    public $verifyPath = null;//验证目录,必填项.命名空间格式填写

    public $whiteParams = null; //过滤之后的参数,供控制器使用

    public $indexName = '';

    public function beforeAction($action)
    {
        if (!$this->enabled) {
            return true;
        }

        return $this->_check($action);
    }

    protected function _check($action)
    {
        if (empty($this->verifyPath)) {
            throw new Exception('验证目录不存在!');
        }

        //目前只支持两级
        $groups = explode('/', $this->owner->id);
        if (is_array($groups) && count($groups) >= 2) {
            $fileName = ucfirst($groups[0]).ucfirst($groups[1]);
        } else {
            $fileName = ucfirst($this->owner->id);
        }
        unset($groups);
        $className = $this->verifyPath . $fileName;

        if (!class_exists($className)) {
            return true;
        }

        $actionId = $action->id;
        
        $v = new $className($this->owner->getParam($this->indexName) ?: []);

        if (!method_exists($v, $actionId)) {
            return true;
        }

        $v->{$actionId}();
        if (!$v->validate(null, false)) {
            $errorList = [];
            $errors = $v->getErrors();

            foreach ($errors as $field => $error) {
                $errorList[] = implode(' ', $error);
            }

            Yii::$app->getResponse()->data = $this->owner->getBehavior('format')->afterAction($action, $this->owner->sendError(implode(' & ', $errorList), 400));
            return false;
        }
        $this->whiteParams = $v->getAttributes();
        return true;
    }
}

在Controller中的behaviors中添加一条validation即可对某些action进行验证啦。

'validation' => [
                'class' => 'ext\controller\behavior\Validation',
                'verifyPath' => 'app\modules\goods\controller\validation\\',
                'indexName' => 'body',
                'enabled' => true
            ],

verifyPath即是被验证的规则路径。如果是验证参数,就对Controller下的全体action进行验证咯。

再来看下是如何编写这些个规则的。创建对应modules的validation。

<?php
namespace app\modules\goods\controllers\validation;

use app\modules\api\models\base\BaseValidation;

class SupplyGoods extends BaseValidation
{

    public function detail()
    {
        $this->defineAttributes('goodsId');
        $this->addRule('goodsId', 'number')->addRule('goodsId', 'required');
    }

    public function add()
    {
        $this->defineAttributes('goodsTitle,goodsPrice,minNum,catId,childCatId,showImages,detailImages,goodsAttr');
        $this->addRule('goodsTitle', 'trim')->addRule('goodsTitle', 'required');

        $this->addRule('goodsPrice', 'required');

        $this->addRule('catId', 'number')->addRule('catId', 'required')
            ->addRule('childCatId', 'number')->addRule('childCatId', 'required');
        

        if (!is_array($this->showImages) || count($this->showImages) == 0) {
            $this->addError('showImages', '商品展示图片必传!');
        }

        if (!is_array($this->detailImages) || count($this->detailImages) == 0) {
            $this->addError('detailImages', '图文详情必传!');
        }

        if (empty($this->goodsAttr['color'])
            || empty($this->goodsAttr['style_id'])
            || empty($this->goodsAttr['size_ids'])
        ) {
            $this->addError('goodsAttr', '请设置库存');
        }
    }

    public function update()
    {
        $this->defineAttributes('goodsId,goodsTitle,goodsPrice,minNum,catId,childCatId,showImages,detailImages,goodsAttr');
        $this->addRule('goodsTitle', 'trim')->addRule('goodsTitle', 'required');

        $this->addRule('goodsPrice', 'required');

        $this->addRule('catId', 'number')->addRule('catId', 'required')
            ->addRule('childCatId', 'number')->addRule('childCatId', 'required')
            ->addRule('goodsId','number')->addRule('goodsId','required');


        if (!is_array($this->showImages) || count($this->showImages) == 0) {
            $this->addError('showImages', '商品展示图片必传!');
        }

        if (!is_array($this->detailImages) || count($this->detailImages) == 0) {
            $this->addError('detailImages', '图文详情必传!');
        }

        if (empty($this->goodsAttr['color'])
            || empty($this->goodsAttr['style_id'])
            || empty($this->goodsAttr['size_ids'])
        ) {
            $this->addError('goodsAttr', '图片属性必传!');
        }
    }

    public function edit()
    {
        $this->defineAttributes('goodsId');
        $this->addRule('goodsId', 'number')->addRule('goodsId', 'required');
    }

    public function lists()
    {

    }

    public function delete()
    {
        $this->defineAttributes('goodsId');
        $this->addRule('goodsId', 'number')->addRule('goodsId', 'required');
    }

    public function category()
    {
        $this->defineAttributes('childCatId');
        $this->addRule('childCatId', 'number')->addRule('childCatId', 'required');
    }

}

BaseValidation是我写的一个基类,对某些全局需要验证的属性进行验证,继承DynamicModel。

验证类的名称SupplyGoods是根据路径来的,我的GoodsController在controllers里面的supply目录下,所以命名成SupplyGoods。

addRule之前必须要定义attributes

$this->defineAttributes('childCatId');
$this->addRule('childCatId', 'number')->addRule('childCatId', 'required');

每个function的名称对应action后面的操作ID。

这是我自己定义的一个过滤器,其实,Yii提供了一组常用过滤器,在yii\filters命名空间下,有些还是很不错的,大家感兴趣的话可以去官网上瞧瞧。

原文链接:https://www.cnblogs.com/riverdubu/p/6607373.html