前言:最近在用php写一个项目的接口,所以需要学习一下Yii的框架,也在这里记录一下。
1.整体入门
1*|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控制器接受出入并将其转换为模型和视图命令。
这是官网上面的框架结构设计,MVC就是其中的控制器,视图和模型,他们的各自作用上面也讲了下,一般的后端应用,M表示从数据库、第三方链接、本地文件中获取的数据进行处理,整理,在交给到V端,V端的作用一般是在页面中反馈给用户的页面,如果是以数据的形式返回给用户,那这个V层就不用做过多的渲染。C层的话主要是连接两者的作用,C层获取到用户的请求,传给M层,M层处理好数据,反馈给C层,C层再将数据给到V层,V层展示给用户。MVC模型的便捷之处就是逻辑清晰,每个模块负责自己的事,有条有理,非常便于初学者理解,是一个入门的模型。
除此之外,Yii还包含其他逻辑处理块,比方说上面图中的入口脚本【调用应用一开始必被调用的脚本文件】,应用主体【Yii::$app全局可访问对象】,应用组件【全局通用的一些工具集】,模块【业务逻辑单元,每个业务逻辑一个模块,会让代码很清晰】,过滤器【规范行为的对象,在控制器执行之前或之后调用,定义一类特殊的行为】,前端资源和小部件我们先不讲,因为是涉及到前端的一些组件内容,后面我会单独开辟一个系列来讲前端知识,我出这一系列的目的主要是针对后台应用~
2*|0*入口脚本
web文件夹下面的index.php就是入口脚本,每次web请求都必须经过它!
还有个入口脚本是啥呢,控制台脚本,下面的那个叫yii的php脚本,啥作用呢,你们想想啊,电商后台中,如果有很多人要调整库存,是不是调整一次就给改一次呀,肯定不会呀,库存操作如果调用数据库太频繁了,数据库肯定扛不住的,我们的做法就是先放到类似于Redis的缓存中,等到一定量的时候,或者有个1秒钟的时候我们给同步一次数据库,同步的方式就是调用控制台脚本啦,配合Linux的crontab,完美解决数据库调用过于频繁的问题。控制台脚本后面我们会介绍,一般业务线中用的还挺多的。
入口脚本主要完成以下工作:
- 定义全局常量;
- 注册 Composer 自动加载器;
- 包含 Yii 类文件;
- 加载应用配置;
- 创建一个应用实例并配置;
调用 yii\base\Application::run() 来处理请求。
<?php// comment out the following two lines when deployed to productiondefined('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类的文件路径。3*|0*应用主体配置
应用主体在入口脚本中创建并能通过表达式
\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、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。
例如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()。4*|0*应用组件
在同一个应用中,每个应用组件都有一个独一无二的 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,这些都是在日常工作中用的比较多的,必配的配置。5*|0*控制器
MVC里面的C,下面我们称它为controller,因为他是入口,我入口脚本一样,用户发来请求,首先到的就是控制器(这里的先到只是在后台应用中先到,如果包含前端资源,那么肯定是先接触到前端代码的)
Controller的主要职责是接受用户请求,分发处理用户请求,拿到结果数据,递交给V层展示给客户
首先继承yii\base\Controller类的对象
Controller由操作【action】组成,他是执行终端用户请求的最基础的单元,一个控制器可以有多个或一个操作
如下示例显示包含两个操作view和create的控制器post:
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直接去加参数,这样不安全。一般使用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:version
分别填入‘123’和‘2’`http://hostname/index.php?r=post/view: 会抛出yii\web\BadRequestHttpException 异常 因为请求没有提供参数给必须赋值参数$idhttp://hostname/index.php?r=post/view&id[]=123: 会抛出yii\web\BadRequestHttpException 异常 因为$id参数收到数字值['123']而不是字符串6*|0*模型
学习完了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一样,写业务逻辑就可以啦。7*|0*视图
后端接口中的应用,这个模块很少被使用到,如果大姐对这个模块感兴趣,可以去官网详查,我这边的方法很简单宝莉,直接return给用户数据即可。
不要忘了,前端需要什么样的数据格式,我们这边需要在最后return的时候encode一下哈。最常用的就是josn格式啦。public function formatJson($data = []){Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;return $data;}
8*|0*模块
你可能没有在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'];
主体应用中配置的模块就指向刚刚建立的模块基础类。
每个模块可以有自己独立的配置文件,将模块中需要用到的共同的参数放到这个配置文件中,并在模块基础类中添加进去。
下面就是我的用户模块的目录和代码
<?phpnamespace 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;
模块在大型项目中常被使用,这些项目的特性可分组,每个组包含一些强相关的特性, 每个特性组可以做成一个模块由特定的开发人员和开发组来开发和维护。
在特性组上,使用模块也是重用代码的好方式,一些常用特性,如用户管理,评论管理,可以开发成模块, 这样在相关项目中非常容易被重用。无耻的抄袭了官网文档的最佳实践,其实个人感觉官网文档的最佳实践是最给力的内容。9*|0*过滤器
在模块那一节,我们提到一个校验的神器,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() 方法来创建动作的过滤器,前者在动作执行之前执行,后者在动作执行之后执行。
下面是我在项目中使用的公共验证器。
<?phpnamespace 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。
<?phpnamespace 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
2.细节
1.Yii 2.0 Activeform 表单部分组件使用方法 [ 2.0 版本 ]
参数model来自我们定义的model类,并通过render渲染到前端(所填的名必须是model里有的,yii还有依据model的rule生成js的前端检验代码)
文本框: textInput();
密码框: passwordInput();
单选框: radio(), radioList();
复选框: checkbox(), checkboxList();
下拉框: dropDownList();
隐藏域: hiddenInput();
文本域: textarea(['rows'=>3]);
文件上传: fileInput();
提交按钮: submitButton();
重置按钮: resetButtun();
<?php$form = ActiveForm::begin(['action' => ['test/getpost'],'method'=>'post',]); ?><? echo $form->field($model, 'username')->textInput(['maxlength' => 20]) ?><? echo $form->field($model, 'password')->passwordInput(['maxlength' => 20]) ?><? echo $form->field($model, 'sex')->radioList(['1'=>'男','0'=>'女']) ?><? echo $form->field($model, 'edu')->dropDownList(['1'=>'大学','2'=>'高中','3'=>'初中'], ['prompt'=>'请选择','style'=>'width:120px']) ?><? echo $form->field($model, 'file')->fileInput() ?><? echo $form->field($model, 'hobby')->checkboxList(['0'=>'篮球','1'=>'足球','2'=>'羽毛球','3'=>'乒乓球']) ?><? echo $form->field($model, 'info')->textarea(['rows'=>3]) ?><? echo $form->field($model, 'userid')->hiddenInput(['value'=>3]) ?><? echo Html::submitButton('提交', ['class'=>'btn btn-primary','name' =>'submit-button']) ?><? echo Html::resetButton('重置', ['class'=>'btn btn-primary','name' =>'submit-button']) ?><?php ActiveForm::end(); ?>
要显示纯文本,先调用 yii\helpers\Html::encode()-detail) 进行转码, 例如如下代码将用户名在显示前先转码:即会将字符转码,从而去除其特殊含义用法,变成普通字符
<?phpuse yii\helpers\Html;?><div class="username"><?= Html::encode($user->name) ?></div>
要显示HTML内容,先调用 yii\helpers\HtmlPurifier 过滤内容, 例如如下代码将提交内容在显示前先过滤:
<?phpuse yii\helpers\HtmlPurifier;?><div class="post"><?= HtmlPurifier::process($post->text) ?></div>
2.路由
- 隐藏入口脚本可以通过
yii\web\UrlManager::showScriptName = false来实现 - 路由的路径化可以通过
yii\web\UrlManager::enablePrettyUrl = true来实现 - 参数的路径化可以通过路由规则来实现
- 加入假后缀(fake suffix)
.html可以通过yii\web\UrlManager::suffix = '.html'来实现
自定义restful路由 例如: http://api.baojia.local/v1/news/search/111
<?phpreturn [......'rules' => [['class' => 'yii\rest\UrlRule','controller' => 'v1/news','extraPatterns' => ['GET search/<keyword>' => 'search',]],],......];
3.数据库操作
先调用类方法,获取query,之后再补充相关的查询条件(构建条件的语句并不会调起执行),最后再调用发起执行的语句(有显示的就表示执行,有一些是隐含调用的如count)
3.1调用样例:
User::find()->all(); 此方法返回所有数据;User::findOne($id); 此方法返回 主键 id=1 的一条数据(举个例子);User::find()->where(['name' => '小伙儿'])->one(); 此方法返回 ['name' => '小伙儿'] 的一条数据;User::find()->where(['name' => '小伙儿'])->all(); 此方法返回 ['name' => '小伙儿'] 的所有数据;User::find()->orderBy('id DESC')->all(); 此方法是排序查询;User::findBySql('SELECT * FROM user')->all(); 此方法是用 sql 语句查询 user 表里面的所有数据;User::findBySql('SELECT * FROM user')->one(); 此方法是用 sql 语句查询 user 表里面的一条数据;User::find()->andWhere(['sex' => '男', 'age' => '24'])->count('id'); 统计符合条件的总条数;User::find()->andFilterWhere(['like', 'name', '小伙儿']); 此方法是用 like 查询 name 等于 小伙儿的 数据User::find()->one(); 此方法返回一条数据;User::find()->all(); 此方法返回所有数据;User::find()->count(); 此方法返回记录的数量;User::find()->average(); 此方法返回指定列的平均值;User::find()->min(); 此方法返回指定列的最小值 ;User::find()->max(); 此方法返回指定列的最大值 ;User::find()->scalar(); 此方法返回值的第一行第一列的查询结果;User::find()->column(); 此方法返回查询结果中的第一列的值;User::find()->exists(); 此方法返回一个值指示是否包含查询结果的数据行;User::find()->batch(10); 每次取 10 条数据User::find()->each(10); 每次取 10 条数据, 迭代查询
3.2获取query后还可调用的其他方法介绍:
one(): 根据查询结果返回查询的第一条记录。all(): 根据查询结果返回所有记录。count(): 返回记录的数量。 不加参数,默认是记录数,可以指定统计某一列的数量。sum(): 返回指定列的总数。average(): 返回指定列的平均值。min(): 返回指定列的最小值。max(): 返回指定列的最大值。scalar(): 返回查询结果的第一行中的第一列的值。column(): 返回查询结果中的第一列的值。exists(): 返回一个值,该值指示查询结果是否有数据。where(): 添加查询条件with(): 该查询应执行的关系列表。indexBy(): 根据索引的列的名称查询结果。asArray(): 以数组的形式返回每条记录。
3.3关联查询:
ActiveRecord::hasOne():返回对应关系的单条记录,一对一ActiveRecord::hasMany():返回对应关系的多条记录,多对多
- 现在模型内建立连接关系,命名规则getName,name是关联的名字
- 在查询时,使用with(‘order’,’country’),order对应getOrder,也可以order.address
- 关联的方法名:小写get驼峰;传入with()参数不带get小写驼峰
- 关联方法里面:如果使用了select()指明返回列,一定要带上关联的列,否则不返回关联的model
- 用with()方法,貌似会调getDb()可跨库关联;joinwith(),不会调getDb(),需要同库,生成的是join on语句 ```php //客户表Model:CustomerModel //订单表Model:OrdersModel //国家表Model:CountrysModel //首先要建立表与表之间的关系 //在CustomerModel中添加与订单的关系
Class CustomerModel extends yiidbActiveRecord { …
public function getOrders(){//客户和订单是一对多的关系所以用hasMany//此处OrdersModel在CustomerModel顶部别忘了加对应的命名空间//id对应的是OrdersModel的id字段,order_id对应CustomerModel的order_id字段return $this->hasMany(OrdersModel::className(), ['id'=>'order_id']);}public function getCountry(){//客户和国家是一对一的关系所以用hasOnereturn $this->hasOne(CountrysModel::className(), ['id'=>'Country_id']);}....
}
// 查询客户与他们的订单和国家 CustomerModel::find()->with(‘orders’, ‘country’)->all();
// 查询客户与他们的订单和订单的发货地址, 显示关联的指定字段 CustomerModel::find()->with(‘orders.address’)->all();
// 查询客户与他们的国家和状态为1的订单 CustomerModel::find()->with([ ‘orders’ => function ($query) { $query->andWhere(‘status = 1’); }, ‘country’, ])->all();
🔺:在使用查询时使用select()参数要加 **关联的从表的字段名**,不然报错```phpCustomerModel::find()->select('order_id')->with('orders', 'country')->all();
3.4findOne跟findAll()的对比
3.5查询条件
cond)->all();
$cond写法举例:
// SQL: (type = 1) AND (status = 2).$cond = ['type' => 1, 'status' => 2]// SQL:(id IN (1, 2, 3)) AND (status = 2)$cond = ['id' => [1, 2, 3], 'status' => 2]//SQL:status IS NULL$cond = ['status' => null]
[[and]]:将不同的条件组合在一起,用法举例:
//SQL:`id=1 AND id=2`$cond = ['and', 'id=1', 'id=2']//SQL:`type=1 AND (id=1 OR id=2)`$cond = ['and', 'type=1', ['or', 'id=1', 'id=2']]
[[or]]:
//SQL:`(type IN (7, 8, 9) OR (id IN (1, 2, 3)))`$cond = ['or', ['type' => [7, 8, 9]], ['id' => [1, 2, 3]]
[[not]]:
//SQL:`NOT (attribute IS NULL)`$cond = ['not', ['attribute' => null]]
[[between]]: not between 用法相同
//SQL:`id BETWEEN 1 AND 10`$cond = ['between', 'id', 1, 10]
[[in]]: not in 用法类似
//SQL:`id IN (1, 2, 3)`$cond = ['in', 'id', [1, 2, 3]]//IN条件也适用于多字段$cond = ['in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]]//也适用于内嵌sql语句$cond = ['in', 'user_id', (new Query())->select('id')->from('users')->where(['active' => 1])]
[[like]]:
//SQL:`name LIKE '%tester%'`$cond = ['like', 'name', 'tester']//SQL:`name LIKE '%test%' AND name LIKE '%sample%'`$cond = ['like', 'name', ['test', 'sample']]//SQL:`name LIKE '%tester'`$cond = ['like', 'name', '%tester', false]
[[exists]]: not exists用法类似
//SQL:EXISTS (SELECT "id" FROM "users" WHERE "active"=1)$cond = ['exists', (new Query())->select('id')->from('users')->where(['active' => 1])]
此外,您可以指定任意运算符如下
//SQL:`id >= 10`$cond = ['>=', 'id', 10]//SQL:`id != 10`$cond = ['!=', 'id', 10]
3.6增删改查curd
常用查询:
// WHERE admin_id >= 10 LIMIT 0,10User::find()->select('*')->where(['>=', 'admin_id', 10])->offset(0)->limit(10)->all()// SELECT `id`, (SELECT COUNT(*) FROM `user`) AS `count` FROM `post`$subQuery = (new Query())->select('COUNT(*)')->from('user');$query = (new Query())->select(['id', 'count' => $subQuery])->from('post');// SELECT DISTINCT `user_id` ...User::find()->select('user_id')->distinct();
更新:
//update();//runValidation boolen 是否通过validate()校验字段 默认为true//attributeNames array 需要更新的字段$model->update($runValidation , $attributeNames);//updateAll();//update customer set status = 1 where status = 2Customer::updateAll(['status' => 1], 'status = 2');//update customer set status = 1 where status = 2 and uid = 1;Customer::updateAll(['status' => 1], ['status'=> '2','uid'=>'1']);
删除:
$model = Customer::findOne($id);$model->delete();$model->deleteAll(['id'=>1]);
批量插入:
Yii::$app->db->createCommand()->batchInsert(UserModel::tableName(), ['user_id','username'], [['1','test1'],['2','test2'],['3','test3'],])->execute();
查看执行sql
//UserModel$query = UserModel::find()->where(['status'=>1]);echo $query->createCommand()->getRawSql();
4.restfulAPI路由定义

修改web.php文件下的urlManager配置信息
'urlManager' => ['enablePrettyUrl' => true,'showScriptName' => false,'rules' => [['class' => 'yii\rest\UrlRule','controller' => 'test','pluralize' => false,'extraPatterns' => ['GET test123/<id:\d+>' => 'test-get']],],],
rules接收一个数组
- class指定构建url的类
- controller,此路由规则所要对应的控制器
- 自定义的路由、规则要写在extralPatterns里面
- 注意pluralize,关闭自动复数话,因为当我们定义了一个不是复数的带可变参数的路由来访问时,urlRule会自动把参数前的复数化,导致不一致而无法访问
如果pluralize不设置为falsehttp://api.52hidao.com/auth/testhttp://api.52hidao.com/auths/test/11注意上面两个url,第一个test后没有具体id,auth不需要复数第二个test后有具体id,auth就要加s,有复数(然后我们定义的时候没有复数,会以为不要复数才能访问,其实已经被转成复数了)所以还是把自动复数化给false了吧。
为了便捷地管理路由,可以单独一个文件route.php,然后在web.php文件中引入$route = require(‘route.php’),之后在web.php文件中对应位置配置 ‘urlManager’ => $route
5.模块的使用
模块在大型项目中使用
🔺任何一个模块的创建都必须要在web.php中声明(同时也可单独创建文件,再引入到web.php中)
5.1定义
- 在项目根目录下如basic下创建modules文件夹
- 在文件夹下创建自定义的文件夹,如goods模块
- 再在goods模块文件夹下创建自动加载的模块入口类文件(建议跟模块名同名),该类文件继承\yii\base\module
- 重载init()函数 ,以便模块进行初始化时配置
- 在web.php声明模块入口类


- 定义模块下的控制器并尝试访问


5.2子模块的创建
子模块,顾名思义,就是在模块下再次创建模块,可以参照5.1的步骤,
- 声明子模块的配置信息modules是在模块的配置信息config.php中
- 子模块同样也能有自己独立的mvc和配置
-
5.3应用主题使用模块
//获取子模块$goods = \YII::$app -> getModule('goods');//声明的模块名id//调用子模块的操作$goods -> runAction('good/index');//在实例化后,调用控制器方法
5.4配置信息注意
创建模块的控制器时,惯例是将控制器类放在模块类命名空间的
controllers子命名空间中, 也意味着要将控制器类文件放在模块 base path 目录中的controllers子目录中。- 可配置 yii\base\Module::controllerMap 属性让它们能被访问, 这类似于 应用主体配置 所做的。(要么赋值,要么就直接覆盖直接赋值)
3.配置信息详解


