Yii2.0基础框架
缘起
因为一个月的短暂停留,我在给朋友搞事情,所以Yii系列的文章耽搁了很长时间,现在又重拾当时的知识,给大伙好好撸下这一系列的博客
提起Yii,虽然是国外的开发者搞的,但是它的作者是华人,这才是让我们引以为豪的,如果以后有机会薛强回国大力发展PHP事业,我肯定回去他麾下搞事情,为PHP在国内的发展贡献自己的一份力,虽然现在没有这个能力,这不薛强没回来嘛,回来的话那时的我肯定可以的~哈哈哈~
领英上强哥的背景如下:
职务:MicroStrategy 公司担任构架师
地址:美国首都华盛顿 Metro Area
教育:
- 杜克大学·计算机科学·博士
- 浙江大学·计算机科学·硕士
好了,话步前言,上节我们已经将Yii2.0完整的安装到我们的机器中啦,在浏览器中输入下面的地址,你就可以访问你的服务器应用啦~
http://服务器IP/app/yii/web/index.php
出现下面的命令那就对啦~
整体结构
assets文件夹: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主应用入口脚本存放的位置
以上是整个文件夹的布局,里面的各个文件有啥用,我会在后续的【应用Yii2.0搭建后台应用框架】中详细介绍
我这一系列的文章均来自于Yii中文网中对Yii2.0的权威指南,感谢国内开发者对社区的贡献,感谢翻译的人,让我们免去了百度翻译之苦。
如果大家不太喜欢我的描述,可以去看一下官方对Yii2.0的解释,很详细,但很官方,不会调戏你^_^
官方文档【中文版】:http://www.yiichina.com/doc/guide/2.0
Yii 应用参照模型-视图-控制器 (MVC)设计模式来组织。不懂MVC?这么说吧,不管是前端应用还是后端应用,首当其冲的设计模式就是MVC。所以了解它相当有必要!
M模型代表数据、业务逻辑和规则;V视图展示模型的输出;C控制器接受出入并将其转换为模型和视图命令。
这就是Yii的整个框架结构设计,我们的MVC就是其中的控制器,视图和模型,他们的各自作用上面也讲了下,一般的后端应用,M表示从数据库、第三方链接、本地文件中获取的数据进行处理,整理,在交给到V端,V端的作用一般是在页面中反馈给用户的页面,如果是以数据的形式返回给用户,那这个V层就不用做过多的渲染。C层的话主要是连接两者的作用,C层获取到用户的请求,传给M层,M层处理好数据,反馈给C层,C层再将数据给到V层,V层展示给用户。MVC模型的便捷之处就是逻辑清晰,每个模块负责自己的事,有条有理,非常便于初学者理解,是一个入门的模型。
除此之外,Yii还包含其他逻辑处理块,比方说上面图中的入口脚本【调用应用一开始必被调用的脚本文件】,应用主体【Yii::$app全局可访问对象】,应用组件【全局通用的一些工具集】,模块【业务逻辑单元,每个业务逻辑一个模块,会让代码很清晰】,过滤器【规范行为的对象,在控制器执行之前或之后调用,定义一类特殊的行为】,前端资源和小部件我们先不讲,因为是涉及到前端的一些组件内容,后面我会单独开辟一个系列来讲前端知识,我出这一系列的目的主要是针对后台应用~
入口脚本
心细的朋友可能早就发现了,为啥我们在上面的访问链接中后面有个index.php,对,就是它,它就是入口脚本,每次web请求都必须经过它!
http://172.16.122.58/app/yii/web/index.php
一般他都是在web这个目录下面的,这个是web应用的入口脚本。
还有个入口脚本是啥呢,控制台脚本,下面的那个叫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,读过我之前文章的朋友肯定对他有印象,PSR-4,自动加载器哈,这个是注册composer自动加载器的。
Yii.php,包含Yii类的文件路径。
倒数第二行是加载应用配置。最后一行是运行一个应用,这里面的$config【web.php】这个文件会在后面详细解答。
应用主体配置
应用主体在入口脚本中创建并能通过表达式 \Yii::$app
全局范围内访问。访问的变量定义在哪儿呢,由于应用主体配置比较复杂,就是刚刚提到的$config,config文件夹中的web.php文件。后面比较复杂的配置都可以放到单个文件中,这是个技巧,即减少了配置文件的代码行数,也将整个框架清晰很多。
这个里面定义了很多属性,我们来分别看下吧。
<?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、modules。
id
yii\base\Application::id 属性用来区分其他应用的唯一标识ID。一般配置为程序名称。必要属性之一。
basePath
yii\base\Application::basePath 指定该应用的根目录。系统预定义 @app
代表这个路径。 你如果需要require目录内的文件,可以使用这个方式找到对应文件。另外一个必要属性,这个是必须得配置的。
bootstrap
这个属性很实用,它允许你用数组指定启动阶段yii\base\Application::bootstrap()需要运行的组件。一般后端应用中配置个
'bootstrap' => ['log'],
即可。
components
这是最重要的属性,它允许你注册多个在其他地方使用的应用组件。比方说session、log、db和cache啊,都在这里面配置的,具体的下一节应用组件中会讲到。
aliases
该属性允许你用一个数组定义多个别名。数组的key为别名名称,值为对应的路径。
[ 'aliases' => [ '@name1' => 'path/to/path1', '@name2' => 'path/to/path2', ], ]
我的配置里面都是设置的extension扩展类的别名,extension里面放置一些基础调用类,比方说CURL、微信支付等等的。
[ '@ext' => dirname(__DIR__) . '/extensions', ]
language
该属性指定应用展示给终端用户的语言,默认为 en
标识英文。如果需要之前其他语言可以配置该属性。
'language' => 'zh-CN',
modules
该属性指定应用所包含的模块。还记得上面说的业务的分割吗,是的,就是在modules里面进行划分的,对应的目录就是根目录下的modules目录,也是需要配置哒~
[ 'modules' => [ // "booking" 模块以及对应的类 'booking' => 'app\modules\booking\BookingModule', // "comment" 模块以及对应的配置数组 'comment' => [ 'class' => 'app\modules\comment\CommentModule', 'db' => 'db', ], ], ]
我的模块的划分
'modules' => [ 'admin' => [ 'class' => 'app\modules\admin\Admin', ], 'api' => [ 'class' => 'app\modules\api\Api', ], 'user' => [ 'class' => 'app\modules\user\User', ], 'bus' => [ 'class' => 'app\modules\bus\Bus', ], 'goods' => [ 'class' => 'app\modules\goods\Goods', ],... ]
这个配置对你代码的结构清晰性有很大的提升,后端应用必配属性。
defaultRoute
该属性指定未配置的请求的响应 路由规则,路由规则可能包含模块ID,控制器ID,动作ID。 例如help
, post/create
, admin/post/create
,如果动作ID没有指定,会使用yii\base\Controller::defaultAction中指定的默认值。
对于 yii\web\Application 网页应用,默认值为 'site'
对应 SiteController
控制器,并使用默认的动作。
对于 yii\console\Application 控制台应用, 默认值为 'help'
对应 yii\console\controllers\HelpController::actionIndex()。
说完了基础的主体配置,其他的主体配置可以去官方文档中查看哟,那里面很详细,不过我还是针对我使用过的和大伙聊聊。
我们来看下components组件配置有哪些关键的配置。
应用组件
应用主体配置完毕,我们来看看其中components有哪些关键配置吧
在同一个应用中,每个应用组件都有一个独一无二的 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
代表代表各种缓存存储器,例如内存,文件,数据库。具体会在【Yii系列】操作缓存一章中讲解
db
代表一个可以执行数据库操作的数据库连接。具体会在【Yii系列】连接数据库一章中讲解
search
搜索组件入口配置,以后有机会和大家唠唠solr相关,切割关键字,切割,切割~
urlManager
支持URL地址解析和创建。具体会在【Yii系列】请求处理一章中讲解
user
代表认证登录用户信息,仅在yii\web\Application 网页应用中可用。如果有后台用户权限认证管理,可去官网了解明细。
session
代表会话信息,仅在yii\web\Application 网页应用中可用,我们一般都是通过这个来控制用户属性的,具体会在【Yii系列】请求处理一章详细讲解
errorHandler
处理 PHP 错误和异常
log
全局日志配置入口,具体会在【Yii系列】请求处理一章讲解
请谨慎注册太多应用组件,应用组件就像全局变量,使用太多可能加大测试和维护的难度。 一般情况下可以在需要时再创建本地组件。
大家可以着重关注几个配置:db、urlManager、log和cache,这些都是在日常工作中用的比较多的,必配的配置。
控制器
讲完了上面的配置文件,我们再来看看组成Yii最最关键的部分,也是大家工作过程中打交道最多的部分啦,请打起你们十二分的精神,看好每一个字符哟~
首先我们来看下MVC中的C,下面我们就成他为Controller,因为他是入口,就和入口脚本一样,用户发来请求,首先到的就是控制器(只是在后台应用中先到,如果是包含前端资源,用户首先接触到的肯定是前端的代码哈)。
Controller的主要责任是接受用户请求,分发处理用户请求,拿到结果数据,递交给V层展示给用户。
继承yii\base\Controller类的对象
Controller由操作【action】组成,它是执行终端用户请求的最基础的单元,一个控制器可有一个或多个操作。
如下示例显示包含两个操作view
和 create
的控制器post
:
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, ]); } } }
用户访问的时候就是直接被只想到某个action里面的,有这个action方法了,那么我们如何访问它呢,这边就涉及到一个被称为路由的东西。
终端用户通过所谓的【路由】寻找到操作
路由使用如下格式:
ControllerID/ActionID
模块下的控制器,使用如下格式:
ModuleID/ControllerID/ActionID
如果用户的请求地址为 http://hostname/index.php?r=site/index
, 会执行site
控制器的index
操作。是的,有过web基础的朋友可能对这个比较熟悉,?后面的是参数,而这个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里面,控制器类必须能被自动加载。
控制器Id可包含子目录前缀,例如 admin/article
代表 yii\base\Application::controllerNamespace控制器命名空间下 admin
子目录中 article
控制器。
下面为一些示例,假设yii\base\Application::controllerNamespace控制器命名空间为 app\controllers
:
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'; } }
独立操作我们不做深究,感兴趣的朋友可以去研究下yii自带的验证码模块源码。
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']
而不是字符串.
关于Controller的生命周期及其他这边没有提到的,可以去官网看详细的解释。
模型
讲完了入口的Controller,现在我们来说说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操作的数据均是来源于用户的,我们需要做的更多的是对用户输入的控制。你可能在日常的工作中对这些用户输入的控制直接就使用了,枫爷在这边给大家一个小小的警醒,千万不能这么干,有几步还是需要做的,为了安全性。其一,xss攻击的防范;其二,用户输入的验证。
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一下哈。最常用的莫过于json格式啦。
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' ];
主体应用中配置的模块就指向刚刚建立的模块基础类。
每个模块可以有自己独立的配置文件,将模块中需要用到的共同的参数放到这个配置文件中,并在模块基础类中添加进去。
下面就是我的用户模块的目录和代码
<?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
命名空间下,有些还是很不错的,大家感兴趣的话可以去官网上瞧瞧。