在 Windwos 10、PHP 7.1.12 下 基于 Yii 2 Starter Kit,复制后台应用为接口应用,实现 RESTful Web 服务的流程
1、在 Windows PowerShll 中,进入 E:\wwwroot\cmcp-api 目录,如图1
2、新建应用api,复制backend目录为api,如图2
3、编辑 \.env.dist,新增 api 相关的配置,此为提交至Git版本控制的配置文件,如图3
API_HOST_INFO = http://api.yii2-starter-kit.dev #API_HOST_INFO = http://yii2-starter-kit.dev #API_BASE_URL = /api/web API_COOKIE_VALIDATION_KEY = <generated_key>
4、编辑 \.env,新增 api 相关的配置,此为实际生效的配置文件,如图4
API_HOST_INFO = http://www.cmcp-api.localhost #API_HOST_INFO = http://yii2-starter-kit.dev #API_BASE_URL = /api/web API_COOKIE_VALIDATION_KEY = <generated_key>
5、编辑 \common\config\bootstrap.php,配置接口应用的别名,如图5
Yii::setAlias('@api', realpath(__DIR__.'/../../api')); Yii::setAlias('@apiUrl', env('API_HOST_INFO') . env('API_BASE_URL') );
6、编辑 \common\config\base.php,配置接口应用的语言包文件,如图6
'api'=>'api.php',
7、新建接口应用的中文语言包文件,复制 \common\messages\zh\backend.php 为 \common\messages\zh\api.php(如果需要支持其他的语言,可以在其他的语言目录下新建),如图7
8、编辑 \common\config\base.php,配置接口应用的URL的解析与生成,如图8
'urlManagerApi' => \yii\helpers\ArrayHelper::merge( [ 'hostInfo' => env('API_HOST_INFO'), 'baseUrl' => env('API_BASE_URL'), ], require(Yii::getAlias('@api/config/_urlManager.php')) ),
9、编辑 \console\controllers\AppController.php,搜索此文件有6处backend,则相应复制3份为api,如图9
public $writablePaths = [ '@common/runtime', '@frontend/runtime', '@frontend/web/assets', '@backend/runtime', '@backend/web/assets', '@api/runtime', '@api/web/assets', '@storage/cache', '@storage/web/source' ]; public $executablePaths = [ '@backend/yii', '@api/yii', '@frontend/yii', '@console/yii', ];
10、编辑 \autocompletion.php,新增接口应用相关的配置,如图10
* @property yii\web\UrlManager $urlManagerApi UrlManager for api application.
11、在目录api中搜索backend\、backend/、=> ‘backend’、BACKEND,严格匹配大小写,将其分别替换为api\、api/、=> ‘api’、API,如图11
backend\ 替换为: api\ backend/ 替换为: api/ => 'backend' 替换为: => 'api' BACKEND 替换为: => API
12、在 \tests 目录中,还有相应的与新增接口应用相关的配置,此部分暂缓一下,到需要进行自动化测试的时候,再来进行配置了。查看 \api\web\index-test.php,如图12
13、在 Windows PowerShll 中,进入 E:\wwwroot\cmcp-api 目录,再次运行命令:php console/yii app/setup,设置可写、可执行权限,如图13
14、在 Nginx 配置文件中新增虚拟主机,重启Nginx,如图14
## API ## server { listen 80; ## 监听 ipv4 上的 80 端口 # listen [::]:80 default_server ipv6only=on; ## 监听 ipv6 上的 80 端口 root E:/wwwroot/cmcp-api/api/web; index index.php index.html; server_name www.cmcp-api.localhost; charset utf-8; access_log logs/www.cmcp-api.localhost.access.log; error_log logs/www.cmcp-api.localhost.error.log; client_max_body_size 128M; # There is a VirtualBox bug related to sendfile that can lead to # corrupted files, if not turned-off on Vagrant based setup # sendfile off; location / { # 如果找不到真实存在的文件,把请求分发至 index.php try_files $uri $uri/ /index.php?$args; } # location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|pdf|ppt|txt|bmp|rtf|js)$ { # access_log off; # expires max; # } location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass php-fpm; fastcgi_index index.php; include fastcgi_params; } location ~ /\.(ht|svn|git) { deny all; } }
15、在 hosts 文件中新增配置,如图15
# 内容管控平台接口 127.0.0.1 cmcp-api.localhost www.cmcp-api.localhost frontend.cmcp-api.localhost backend.cmcp-api.localhost storage.cmcp-api.localhost
16、打开 http://www.cmcp-api.localhost, 如果出现样式文件加载响应404的情况,可以删除 \api\web\assets 下的所有子目录,如图16
17、准备将创建页面的功能基于接口实现,如图17
18、调整 Page 模型的相关结构,实现模型分层,参考网址:http://www.shuijingwanwq.com/2017/08/15/1713/ ,如图18
19、打开网址:http://frontend.cmcp-api.localhost/gii ,如图19
20、基于数据库表 ca_page 建立相应模型,如图20
21、在common目录中新建logics目录,用于MySQL模型的逻辑层所在目录,如图21
22、复制 \common\models\Page.php 至 \common\logics\Page.php,如图22
<?php namespace common\models; use Yii; use yii\behaviors\SluggableBehavior; use yii\behaviors\TimestampBehavior; use yii\db\ActiveRecord; /** * This is the model class for table "page". * * @property integer $id * @property string $slug * @property string $title * @property string $body * @property string $view * @property integer $status * @property integer $created_at * @property integer $updated_at */ class Page extends ActiveRecord { const STATUS_DRAFT = 0; const STATUS_PUBLISHED = 1; /** * @inheritdoc */ public static function tableName() { return '{{%page}}'; } /** * @inheritdoc */ public function behaviors() { return [ TimestampBehavior::className(), 'slug' => [ 'class' => SluggableBehavior::className(), 'attribute' => 'title', 'ensureUnique' => true, 'immutable' => true ] ]; } /** * @inheritdoc */ public function rules() { return [ [['title', 'body'], 'required'], [['body'], 'string'], [['status'], 'integer'], [['slug'], 'unique'], [['slug'], 'string', 'max' => 2048], [['title'], 'string', 'max' => 512], [['view'], 'string', 'max' => 255] ]; } /** * @inheritdoc */ public function attributeLabels() { return [ 'id' => Yii::t('common', 'ID'), 'slug' => Yii::t('common', 'Slug'), 'title' => Yii::t('common', 'Title'), 'body' => Yii::t('common', 'Body'), 'view' => Yii::t('common', 'Page View'), 'status' => Yii::t('common', 'Active'), 'created_at' => Yii::t('common', 'Created At'), 'updated_at' => Yii::t('common', 'Updated At'), ]; } }
23、基于 diff ,编辑 \common\logics\Page.php,如图23
24、在common/logics目录中的MySQL模型文件为业务逻辑相关,继承至 \common\models\Page 数据层,如图24
<?php namespace common\logics; use Yii; use yii\behaviors\SluggableBehavior; use yii\behaviors\TimestampBehavior; class Page extends \common\models\Page { const STATUS_DRAFT = 0; const STATUS_PUBLISHED = 1; /** * @inheritdoc */ public function behaviors() { return [ TimestampBehavior::className(), 'slug' => [ 'class' => SluggableBehavior::className(), 'attribute' => 'title', 'ensureUnique' => true, 'immutable' => true ] ]; } /** * @inheritdoc */ public function rules() { return [ [['title', 'body'], 'required'], [['body'], 'string'], [['status'], 'integer'], [['slug'], 'unique'], [['slug'], 'string', 'max' => 2048], [['title'], 'string', 'max' => 512], [['view'], 'string', 'max' => 255], ]; } }
25、点击Model Generator下的Start按钮,生成模型Page,命名空间为common\models,此时需支持国际化,覆盖\common\models\Page.php,如图25
26、编辑 \common\config\base.php,当源语言和目标语言相同时,是否强制进行消息翻译,默认为假,设置为真,如图26
'*'=> [ 'class' => 'yii\i18n\PhpMessageSource', 'forceTranslation' => true, 'basePath'=>'@common/messages', 'fileMap'=>[ 'common'=>'common.php', 'backend'=>'backend.php', 'api'=>'api.php', 'frontend'=>'frontend.php', ], 'on missingTranslation' => ['\backend\modules\i18n\Module', 'missingTranslation'] ],
27、新建 \common\messages\en\model\page.php,支持目标语言为英语美国时的消息翻译,如图27
return [ 'ID' => 'ID', 'Slug' => 'Slug', 'Title' => 'Title', 'Body' => 'Body', 'View' => 'Page View', 'Status' => 'Active', 'Created At' => 'Created At', 'Updated At' => 'Updated At', ];
28、新建 \common\messages\zh\model\page.php,支持目标语言为简体中文时的消息翻译,如图28
return [ 'ID' => 'ID', 'Slug' => '别名', 'Title' => '标题', 'Body' => '内容', 'View' => '页面浏览', 'Status' => '活动', 'Created At' => '创建时间', 'Updated At' => '更新时间', ];
29、新建 \api\models\Page.php,在api/models目录中的MySQL模型文件为业务逻辑相关(仅与api相关),继承至 \common\logics\Page 逻辑层,如图29
30、复制\api\models\Page.php 至 \frontend\models\Page.php、\backend\models\Page.php,调整为各自的命名空间,如图30
31、在 api 应用中搜索 use common\models\Page;,替换为:use api\models\Page;,在前台、后台应用中同样类似处理,如图31
32、当本地设置为简体中文时,打开 http://backend.cmcp-api.localhost/page/create ,翻译功能可用,如图32
33、编辑个人信息,设置本地为英语美国,如图33
34、当本地设置为英语美国时,再次打开 http://backend.cmcp-api.localhost/page/create ,翻译功能可用,如图34
35、开始实现 RESTful Web 服务,参考网址:http://www.shuijingwanwq.com/2016/11/28/1457/ ,如图35
36、打开网址:http://backend.cmcp-api.localhost/article/create ,创建文章,如图36
37、在 Postman 中,GET http://frontend.cmcp-api.localhost/api/v1/articles ,此为自带的RESTful Web 服务,响应结果,如图37
{ "items": [ { "id": 1, "slug": "biao-ti20180104-0", "category_id": 1, "title": "标题20180104-0", "body": " 内容 ", "published_at": 1515031309, "_links": { "self": { "href": "http://frontend.cmcp-api.localhost/api/v1/articles/1" } } } ], "_links": { "self": { "href": "http://frontend.cmcp-api.localhost/api/v1/articles?page=1" } }, "_meta": { "totalCount": 1, "pageCount": 1, "currentPage": 1, "perPage": 20 } }
38、RESTful Web 服务,建议基于一个单独的接口应用来实现,而不是基于前台应用中的一个api模块来实现,这样可以更为方便地维护你的WEB应用程序,如图38
39、新建目录:\api\rests,此目录将做为 RESTful Web 服务的操作方法类目录,如图39
40、编辑控制器类 \api\controllers\PageController.php ,控制器类扩展自 [[yii\rest\ActiveController]]。 通过指定 [[yii\rest\ActiveController::modelClass|modelClass]] 作为 api\models\Page, 控制器就能知道使用哪个模型去获取和处理数据。如图40
namespace api\controllers; use yii\rest\ActiveController; class PageController extends ActiveController { public $modelClass = 'api\models\Page'; }
41、配置URL规则,修改有关在应用程序配置的urlManager组件的配置,编辑:\api\config\_urlManager.php,如图41
return [ 'class' => yii\web\UrlManager::class, 'enablePrettyUrl' => true, 'showScriptName' => false, 'rules' => [ [ 'class' => 'yii\rest\UrlRule', 'controller' => ['page'], ], ], ];
42、在 Postman 中,GET http://www.cmcp-api.localhost/pages ,403响应,如图42
{ "name": "Forbidden", "message": "Login Required", "code": 0, "status": 403, "type": "yii\\web\\ForbiddenHttpException" }
43、编辑 \api\config\web.php,注释附加的行为globalAccess,有待后期调整(验证、授权),如图43
/* 'as globalAccess' => [ 'class' => common\behaviors\GlobalAccessBehavior::class, 'rules' => [ [ 'controllers' => ['sign-in'], 'allow' => true, 'roles' => ['?'], 'actions' => ['login'] ], [ 'controllers' => ['sign-in'], 'allow' => true, 'roles' => ['@'], 'actions' => ['logout'] ], [ 'controllers' => ['site'], 'allow' => true, 'roles' => ['?', '@'], 'actions' => ['error'] ], [ 'controllers' => ['debug/default'], 'allow' => true, 'roles' => ['?'], ], [ 'controllers' => ['user'], 'allow' => true, 'roles' => ['administrator'], ], [ 'controllers' => ['user'], 'allow' => false, ], [ 'allow' => true, 'roles' => ['manager'], ] ] ] */
44、在 Postman 中,GET http://www.cmcp-api.localhost/pages ,200响应,如图44
[ { "id": 1, "slug": "about", "title": "About", "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "view": null, "status": 1, "created_at": 1514860785, "updated_at": 1514860785 } ]
45、调用API服务后返回数据采用统一格式,返回的HTTP状态码为20x,代表调用成功;返回4xx或5xx的HTTP状态码代表调用失败。调整200响应的返回数据格式,与403响应一致,至少包含:”message”,”code”,新建语言包文件:\api\messages\zh\app.php(响应成功)、\api\messages\zh\error.php(响应失败),如图45
return [ 10000 => 'success', 10001 => '获取页面列表成功', ];
46、版本化的实现,参考网址:https://github.com/yiisoft/yii2/blob/master/docs/guide-zh-CN/rest-versioning.md ,编辑 \api\controllers\PageController.php,删除 modelClass,如图46
<?php namespace api\controllers; use yii\rest\ActiveController; class PageController extends ActiveController { }
47、把每个主要版本的 API 实现在一个单独的模块 ID 的主版本号,基于 Gii 生成模块 v1,打开网址:http://frontend.cmcp-api.localhost/gii/module ,如图47
48、新建 \api\modules\v1\models\Page.php,继承至 \api\models\Page.php,如图48
注:\api\modules\v1\models\Page(仅用于 v1 模块) > \api\models\Page(仅用于 api 应用) > \common\logics\Page.php(可用于 api、frontend 等多个应用) > \common\models\Page.php(仅限于 Gii 生成) > \yii\db\ActiveRecord
<?php namespace api\modules\v1\models; class Page extends \api\models\Page { }
49、\api\modules\v1\controllers\DefaultController.php 重命名为 \api\modules\v1\controllers\PageController.php,编辑代码,如图49
注:\api\modules\v1\controllers\PageController.php(仅用于 v1 模块) > \api\controllers\PageController.php(仅用于 api 应用) > \yii\rest\ActiveController
<?php namespace api\modules\v1\controllers; /** * Page controller for the `v1` module */ class PageController extends \api\controllers\PageController { public $modelClass = 'api\modules\v1\models\Page'; }
50、要在应用中使用模块,只需要将模块加入到应用主体配置的[[yii\base\Application::modules|modules]]属性的列表中, 如下代码的应用主体配置 使用 v1 模块,编辑 \api\config\web.php,如图50
'modules' => [ 'v1' => [ 'class' => api\modules\v1\Module::class, ], 'i18n' => [ 'class' => api\modules\i18n\Module::class, 'defaultRoute' => 'i18n-message/index' ] ],51、配置URL规则,修改有关在应用程序配置的urlManager组件的配置,以支持 v1 模块,编辑:\api\config\_urlManager.php,如图51
注:通过配置 only 选项来明确列出哪些行为支持,v1/page 仅支持:’index’, ‘update’, ‘delete’, ‘options’
<?php return [ 'class' => yii\web\UrlManager::class, 'enablePrettyUrl' => true, 'enableStrictParsing' => true, 'showScriptName' => false, 'rules' => [ [ 'class' => 'yii\rest\UrlRule', 'controller' => ['v1/page'], 'only' => ['index', 'update', 'delete', 'options'], ], ], ];
52、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,如图52
[ { "id": 1, "slug": "about", "title": "About", "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "view": null, "status": 1, "created_at": 1514860785, "updated_at": 1514860785 } ]
53、测试 only 选项,在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/1 ,405响应(不被允许的方法。因为 ‘view’ 行为不被支持),测试通过,如图53
54、配置URL规则,修改有关在应用程序配置的urlManager组件的配置,以支持 v1 模块,编辑:\api\config\_urlManager.php,如图54
注:取消配置 only 选项,以后续支持所有行为
<?php return [ 'class' => yii\web\UrlManager::class, 'enablePrettyUrl' => true, 'enableStrictParsing' => true, 'showScriptName' => false, 'rules' => [ [ 'class' => 'yii\rest\UrlRule', 'controller' => ['v1/page'], ], ], ];
55、在 Postman 中,GET http://www.cmcp-api.localhost/v1 ,404响应,格式为HMTL,如图55
<!DOCTYPE html> <html lang="en-US"> <head> <meta charset="UTF-8"> <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'> <meta name="csrf-param" content="_csrf"> <meta name="csrf-token" content="MtUm8pVxEHfmqhYMruxdUyScsktaMyFDc350jW_XR7dzmGqaoDRyT7bPc13M2W0AVNuGISJSbQUYOFn9LKNzgQ=="> <title>Not Found (#404)</title> <link href="/assets/95b60493/themes/smoothness/jquery-ui.css?v=1514958071" rel="stylesheet"> <link href="/assets/7635f0fe/css/bootstrap.css?v=1512021376" rel="stylesheet"> <link href="/assets/1ee3df8/css/font-awesome.min.css?v=1512021376" rel="stylesheet"> <link href="/assets/541f775b/css/AdminLTE.min.css?v=1512021373" rel="stylesheet"> <link href="/assets/541f775b/css/skins/_all-skins.min.css?v=1512021373" rel="stylesheet"> <link href="/css/style.css?v=1512021342" rel="stylesheet"> </head> <body class=" skin-blue "> </body> </html>
56、对于404响应格式为HTML的解决,编辑 \api\config\web.php,设置默认的响应格式为JSON,如图56
'response' => [ 'format' => yii\web\Response::FORMAT_JSON, ],
57、在 Postman 中,GET http://www.cmcp-api.localhost/v1 ,404响应,格式为JSON,如图57
{ "name": "Not Found", "message": "Page not found.", "code": 0, "status": 404, "type": "yii\\web\\NotFoundHttpException" }
58、数据序列化的实现,在响应主体内包含分页信息来简化客户端的开发工作,编辑 \api\controllers\PageController.php,如图58
<?php namespace api\controllers; use yii\rest\ActiveController; class PageController extends ActiveController { public $serializer = [ 'class' => 'yii\rest\Serializer', 'collectionEnvelope' => 'items', ]; }
59、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,在响应主体内包含分页信息,如图59
{ "items": [ { "id": 1, "slug": "about", "title": "About", "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "view": null, "status": 1, "created_at": 1514860785, "updated_at": 1514860785 } ], "_links": { "self": { "href": "http://www.cmcp-api.localhost/v1/pages?page=1" } }, "_meta": { "totalCount": 1, "pageCount": 1, "currentPage": 1, "perPage": 20 } }
60、RESTful APIs 通常是无状态的,因此,配置 user 应用组件,编辑 \api\config\web.php,如图60
注:配置user 应用组件:
设置 [[yii\web\User::enableSession|enableSession]] 属性为 false.
设置 [[yii\web\User::loginUrl|loginUrl]] 属性为null 显示一个HTTP 403 错误而不是跳转到登录界面.
'user' => [ 'class' => yii\web\User::class, 'identityClass' => common\models\User::class, 'enableSession' => false, 'loginUrl' => null, 'enableAutoLogin' => false, 'as afterLogin' => common\behaviors\LoginTimestampBehavior::class ],
61、复制目录 \vendor\yiisoft\yii2\rest 下的 Action.php、IndexAction.php、ViewAction.php、CreateAction.php、UpdateAction.php、DeleteAction.php、Serializer.php 至目录 \api\rests\page,如图61
62、如果为多个单词组合的目录,建议目录使用小写+下划线,参考网址:https://github.com/hfcorriez/fig-standards/blob/master/accepted/zh_CN/PSR-0.md ,编辑 \api\controllers\PageController.php,如图62
<?php namespace api\controllers; use yii\rest\ActiveController; class PageController extends ActiveController { public $serializer = [ 'class' => 'api\rests\page\Serializer', 'collectionEnvelope' => 'items', ]; /** * @inheritdoc */ public function actions() { return [ 'index' => [ 'class' => 'api\rests\page\IndexAction', 'modelClass' => $this->modelClass, 'checkAccess' => [$this, 'checkAccess'], ], 'view' => [ 'class' => 'api\rests\page\ViewAction', 'modelClass' => $this->modelClass, 'checkAccess' => [$this, 'checkAccess'], ], 'create' => [ 'class' => 'api\rests\page\CreateAction', 'modelClass' => $this->modelClass, 'checkAccess' => [$this, 'checkAccess'], 'scenario' => $this->createScenario, ], 'update' => [ 'class' => 'api\rests\page\UpdateAction', 'modelClass' => $this->modelClass, 'checkAccess' => [$this, 'checkAccess'], 'scenario' => $this->updateScenario, ], 'delete' => [ 'class' => 'api\rests\page\DeleteAction', 'modelClass' => $this->modelClass, 'checkAccess' => [$this, 'checkAccess'], ], 'options' => [ 'class' => 'yii\rest\OptionsAction', ], ]; } }
注:如果仅支持较少的行为,可以选择下面的方案,例
public function actions() { $actions = parent::actions(); $actions['view']['class'] = 'api\rests\page\ViewAction'; return $actions; }
63、编辑 \api\rests\page\IndexAction.php,调整命名空间、继承关系、查询条件等,如图63
<?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\page; use Yii; use yii\data\ActiveDataProvider; /** * IndexAction implements the API endpoint for listing multiple models. * * For more details and usage information on IndexAction, see the [guide article on rest controllers](guide:rest-controllers). * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class IndexAction extends \yii\rest\IndexAction { const STATUS_INACTIVE = 0; //状态:不活跃 const STATUS_ACTIVE = 1; //状态:活跃 /** * Prepares the data provider that should return the requested collection of the models. * @return ActiveDataProvider */ protected function prepareDataProvider() { $requestParams = Yii::$app->getRequest()->getBodyParams(); if (empty($requestParams)) { $requestParams = Yii::$app->getRequest()->getQueryParams(); } $filter = null; if ($this->dataFilter !== null) { $this->dataFilter = Yii::createObject($this->dataFilter); if ($this->dataFilter->load($requestParams)) { $filter = $this->dataFilter->build(); if ($filter === false) { return $this->dataFilter; } } } if ($this->prepareDataProvider !== null) { return call_user_func($this->prepareDataProvider, $this, $filter); } /* @var $modelClass \yii\db\BaseActiveRecord */ $modelClass = $this->modelClass; $query = $modelClass::find()->where(['status' => self::STATUS_ACTIVE]); if (!empty($filter)) { $query->andWhere($filter); } return Yii::createObject([ 'class' => ActiveDataProvider::className(), 'query' => $query, 'pagination' => [ 'params' => $requestParams, ], 'sort' => [ 'params' => $requestParams, ], ]); } }
64、编辑 \api\rests\page\Serializer.php,调整命名空间、继承关系、响应结构(响应成功:”code”: 10000,”message”,”data”;响应失败:”code”: 不等于10000的其他数字,”message”)等,如图64
<?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\page; use Yii; use yii\data\DataProviderInterface; /** * Serializer converts resource objects and collections into array representation. * * Serializer is mainly used by REST controllers to convert different objects into array representation * so that they can be further turned into different formats, such as JSON, XML, by response formatters. * * The default implementation handles resources as [[Model]] objects and collections as objects * implementing [[DataProviderInterface]]. You may override [[serialize()]] to handle more types. * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class Serializer extends \yii\rest\Serializer { /** * Serializes a data provider. * @param DataProviderInterface $dataProvider * @return array the array representation of the data provider. */ protected function serializeDataProvider($dataProvider) { if ($this->preserveKeys) { $models = $dataProvider->getModels(); } else { $models = array_values($dataProvider->getModels()); } $models = $this->serializeModels($models); if (($pagination = $dataProvider->getPagination()) !== false) { $this->addPaginationHeaders($pagination); } if ($this->request->getIsHead()) { return null; } elseif ($this->collectionEnvelope === null) { return $models; } $result = [ $this->collectionEnvelope => $models, ]; if (empty($result['items'])) { return ['code' => 20001, 'message' => Yii::t('error', '20001')]; } if ($pagination !== false) { return ['code' => 10000, 'message' => Yii::t('app', '10001'), 'data' => array_merge($result, $this->serializePagination($pagination))]; } return ['code' => 10000, 'message' => Yii::t('app', '10001'), 'data' => $result]; } }
65、编辑 \api\config\base.php,配置接口应用的 i18n 应用组件 ,如图65
'i18n' => [ 'translations' => [ 'model/*'=> [ 'class' => 'yii\i18n\PhpMessageSource', 'forceTranslation' => true, 'basePath'=>'@common/messages', 'fileMap'=>[ ], ], 'app'=> [ 'class' => 'yii\i18n\PhpMessageSource', 'forceTranslation' => true, 'basePath'=>'@api/messages', 'fileMap'=>[ ], ], '*'=> [ 'class' => 'yii\i18n\PhpMessageSource', 'forceTranslation' => true, 'basePath'=>'@api/messages', 'fileMap'=>[ ], ], ], ],
66、新建语言包文件:\api\messages\zh\error.php(简体中文、响应失败),如图66
return [ 20000 => 'error', 20001 => '页面列表为空', ];
67、新建语言包文件:\api\messages\en\app.php(英语美国、响应成功),如图67
return [ 10000 => 'success', 10001 => 'Get page list is successful', ];
68、新建语言包文件:\api\messages\en\error.php(英语美国、响应失败),如图68
return [ 20000 => 'error', 20001 => 'Page list is empty', ];
69、打开网址:http://backend.cmcp-api.localhost/page/update?id=1 ,编辑文章,活跃复选框取消选中,如图69
70、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,状态为活跃的页面列表为空,如图70
注:
Accept application/json; version=0.0
{ "code": 20001, "message": "Page list is empty" }
71、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,状态为活跃的页面列表为空,如图71
注:
Accept application/json; version=0.0
Accept-Language zh-CN
{ "code": 20001, "message": "页面列表为空" }
72、打开网址:http://backend.cmcp-api.localhost/page/update?id=1 ,编辑文章,活跃复选框勾选,如图72
73、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,状态为活跃的页面列表不为空,如图73
注:
Accept application/json; version=0.0
Accept-Language en-US
{ "code": 10000, "message": "Get page list is successful", "data": { "items": [ { "id": 1, "slug": "about", "title": "About", "body": " Lorem ipsum dolor sit amet, consectetur adipiscing elit. ", "view": "", "status": 1, "created_at": 1514860785, "updated_at": 1515482758 } ], "_links": { "self": { "href": "http://www.cmcp-api.localhost/v1/pages?page=1" } }, "_meta": { "totalCount": 1, "pageCount": 1, "currentPage": 1, "perPage": 20 } } }
74、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,状态为活跃的页面列表不为空,如图74
注:
Accept application/json; version=0.0
Accept-Language zh-CN
{ "code": 10000, "message": "获取页面列表成功", "data": { "items": [ { "id": 1, "slug": "about", "title": "About", "body": " Lorem ipsum dolor sit amet, consectetur adipiscing elit. ", "view": "", "status": 1, "created_at": 1514860785, "updated_at": 1515482758 } ], "_links": { "self": { "href": "http://www.cmcp-api.localhost/v1/pages?page=1" } }, "_meta": { "totalCount": 1, "pageCount": 1, "currentPage": 1, "perPage": 20 } } }
75、GET /pages/1: 返回页面 1 的详细信息,编辑 \api\rests\page\Action.php,调整命名空间、继承关系、响应结构等,如图75
<?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\page; use Yii; use yii\db\ActiveRecordInterface; use yii\web\NotFoundHttpException; /** * Action is the base class for action classes that implement RESTful API. * * For more details and usage information on Action, see the [guide article on rest controllers](guide:rest-controllers). * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class Action extends \yii\rest\Action { /** * Returns the data model based on the primary key given. * If the data model is not found, a 404 HTTP exception will be raised. * @param string $id the ID of the model to be loaded. If the model has a composite primary key, * the ID must be a string of the primary key values separated by commas. * The order of the primary key values should follow that returned by the `primaryKey()` method * of the model. * @return ActiveRecordInterface the model found * @throws NotFoundHttpException if the model cannot be found */ public function findModel($id) { if ($this->findModel !== null) { return call_user_func($this->findModel, $id, $this); } /* @var $modelClass ActiveRecordInterface */ $modelClass = $this->modelClass; $keys = $modelClass::primaryKey(); if (count($keys) > 1) { $values = explode(',', $id); if (count($keys) === count($values)) { $model = $modelClass::findOne(array_combine($keys, $values)); } } elseif ($id !== null) { $model = $modelClass::findOne($id); } if (isset($model)) { return $model; } throw new NotFoundHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '20002'), ['id' => $id])), 20002); } }
76、编辑 \api\rests\page\ViewAction.php,调整命名空间、继承关系、响应结构等,如图76
<?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\page; use Yii; /** * ViewAction implements the API endpoint for returning the detailed information about a model. * * For more details and usage information on ViewAction, see the [guide article on rest controllers](guide:rest-controllers). * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class ViewAction extends Action { const STATUS_INACTIVE = 0; //状态:不活跃 const STATUS_ACTIVE = 1; //状态:活跃 /** * Displays a model. * @param string $id the primary key of the model. * @return \yii\db\ActiveRecordInterface the model being displayed */ public function run($id) { $model = $this->findModel($id); if ($this->checkAccess) { call_user_func($this->checkAccess, $this->id, $model); } /* 判断状态,如果为不活跃,则返回失败 */ if ($model->status === self::STATUS_INACTIVE) { return ['code' => 20003, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20003'), ['id' => $id]))]; } return ['code' => 10000, 'message' => Yii::t('app', '10002'), 'data' => $model]; } }
77、编辑语言包文件:\api\messages\zh\app.php(简体中文、响应成功)、\api\messages\zh\error.php(简体中文、响应失败)、\api\messages\en\app.php(英语美国、响应成功)、\api\messages\en\error.php(英语美国、响应失败),如图77
\api\messages\zh\app.php
return [ 10000 => 'success', 10001 => '获取页面列表成功', 10002 => '获取页面详情成功', ];
\api\messages\zh\error.php
return [ 20000 => 'error', 20001 => '页面列表为空', 20002 => '页面ID:{id},不存在', 20003 => '页面ID:{id},的状态不活跃', ];
\api\messages\en\app.php
return [ 10000 => 'success', 10001 => 'Get page list is successful', 10002 => 'Get page details succeeded', ];
\api\messages\en\error.php
return [ 20000 => 'error', 20001 => 'Page list is empty', 20002 => 'Page ID: {id}, does not exist', 20003 => 'Page ID: {id}, the status is not active', ];
78、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/1 ,200响应,其状态为活跃,如图78
注:
Accept application/json; version=0.0
Accept-Language zh-CN
{ "code": 10000, "message": "获取页面详情成功", "data": { "id": 2, "slug": "contact", "title": "Contact", "body": " Contact ", "view": "", "status": 1, "created_at": 1515488912, "updated_at": 1515488912 } }
79、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/1 ,200响应,其状态为活跃,如图79
注:
Accept application/json; version=0.0
Accept-Language en-US
{ "code": 10000, "message": "Get page details succeeded", "data": { "id": 2, "slug": "contact", "title": "Contact", "body": " Contact ", "view": "", "status": 1, "created_at": 1515488912, "updated_at": 1515488912 } }
80、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/2 ,200响应,其状态为不活跃,如图80
注:
Accept application/json; version=0.0
Accept-Language en-US
{ "code": 20003, "message": "Page ID: 2, the status is not active" }
81、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/3 ,404响应,页面不存在,如图81
注:
Accept application/json; version=0.0
Accept-Language zh-CN
{ "name": "Not Found", "message": "页面ID:3,不存在", "code": 20002, "status": 404, "type": "yii\\web\\NotFoundHttpException" }
82、实现[[yii\web\Linkable]] 接口来支持HATEOAS,返回与本资源对象的相关链接,编辑资源类 \api\models\Page.php,如图82
<?php namespace api\models; use yii\helpers\Url; use yii\web\Linkable; use yii\web\Link; class Page extends \common\logics\Page implements Linkable { /** * Returns a list of links. * * @return array the links */ public function getLinks() { return [ Link::REL_SELF => Url::to(['page/view', 'id' => $this->id], true), 'index' => Url::to(['page/index'], true), 'view' => Url::to(['page/view', 'id' => $this->id], true), 'create' => Url::to(['page/index'], true), 'update' => Url::to(['page/view', 'id' => $this->id], true), 'delete' => Url::to(['page/view', 'id' => $this->id], true), 'options' => Url::to(['page/index'], true), ]; } }83、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/1 ,200响应,支持HATEOAS,如图83
注:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br
{ "code": 10000, "message": "获取页面详情成功", "data": { "id": 1, "slug": "about", "title": "About", "body": " Lorem ipsum dolor sit amet, consectetur adipiscing elit. ", "view": "", "status": 1, "created_at": 1514860785, "updated_at": 1515548927, "_links": { "self": { "href": "http://www.cmcp-api.localhost/v1/pages/1" }, "index": { "href": "http://www.cmcp-api.localhost/v1/pages" }, "view": { "href": "http://www.cmcp-api.localhost/v1/pages/1" }, "create": { "href": "http://www.cmcp-api.localhost/v1/pages" }, "update": { "href": "http://www.cmcp-api.localhost/v1/pages/1" }, "delete": { "href": "http://www.cmcp-api.localhost/v1/pages/1" }, "options": { "href": "http://www.cmcp-api.localhost/v1/pages" } } } }
84、POST /pages: 创建一个新页面,编辑 \api\rests\page\CreateAction.php,如图84
<?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\page; use Yii; use yii\base\Model; use yii\helpers\Url; use yii\web\ServerErrorHttpException; /** * CreateAction implements the API endpoint for creating a new model from the given data. * * For more details and usage information on CreateAction, see the [guide article on rest controllers](guide:rest-controllers). * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class CreateAction extends Action { /** * @var string the scenario to be assigned to the new model before it is validated and saved. */ public $scenario = Model::SCENARIO_DEFAULT; /** * @var string the name of the view action. This property is need to create the URL when the model is successfully created. */ public $viewAction = 'view'; /** * Creates a new model. * @return \yii\db\ActiveRecordInterface the model newly created * @throws ServerErrorHttpException if there is any error when creating the model */ public function run() { if ($this->checkAccess) { call_user_func($this->checkAccess, $this->id); } /* @var $model \yii\db\ActiveRecord */ $model = new $this->modelClass([ 'scenario' => $this->scenario, ]); $model->load(Yii::$app->getRequest()->getBodyParams(), ''); if ($model->save()) { $response = Yii::$app->getResponse(); $response->setStatusCode(201); $id = implode(',', array_values($model->getPrimaryKey(true))); $response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true)); } elseif ($model->hasErrors()) { $response = Yii::$app->getResponse(); $response->setStatusCode(422, 'Data Validation Failed.'); foreach ($model->getFirstErrors() as $message) { $firstErrors = $message; } return ['code' => 20004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20004'), ['firstErrors' => $firstErrors]))]; } elseif (!$model->hasErrors()) { throw new ServerErrorHttpException('Failed to create the object for unknown reason.'); } return ['code' => 10000, 'message' => Yii::t('app', '10003'), 'data' => $model]; } }
85、在 Postman 中,POST http://www.cmcp-api.localhost/v1/pages ,201响应,如图85
注:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded
{ "code": 10000, "message": "创建页面成功", "data": { "slug": "slug-20180110-4", "title": "title-20180110-4", "body": "body-20180110-4", "view": "view-20180110-4", "status": "0", "created_at": 1515566824, "updated_at": 1515566824, "id": 7, "_links": { "self": { "href": "http://www.cmcp-api.localhost/v1/pages/7" }, "index": { "href": "http://www.cmcp-api.localhost/v1/pages" }, "view": { "href": "http://www.cmcp-api.localhost/v1/pages/7" }, "create": { "href": "http://www.cmcp-api.localhost/v1/pages" }, "update": { "href": "http://www.cmcp-api.localhost/v1/pages/7" }, "delete": { "href": "http://www.cmcp-api.localhost/v1/pages/7" }, "options": { "href": "http://www.cmcp-api.localhost/v1/pages" } } } }
86、在 Postman 中,POST http://www.cmcp-api.localhost/v1/pages ,参数保持原样,422响应,如图86
注:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded
{ "code": 20004, "message": "数据验证失败:Slug的值\"slug-20180110-4\"已经被占用了。" }
87、在 Postman 中,POST http://www.cmcp-api.localhost/v1/pages ,422响应(数据验证失败 (例如,响应一个 POST 请求)。 请检查响应体内详细的错误消息。),如图87
注:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded
Body
缺少 title 参数
{ "code": 20004, "message": "数据验证失败:Title不能为空。" }
88、在 Postman 中,POST http://www.cmcp-api.localhost/v1/pages ,422响应,如图88
注:
Accept application/json; version=0.0
Accept-Language en-US
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded
Body
缺少 title 参数
{ "code": 20004, "message": "Data validation failed: Title cannot be blank." }
89、PUT /pages/4: 更新一个页面,编辑 \api\rests\page\UpdateAction.php,如图89
<?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\page; use Yii; use yii\base\Model; use yii\db\ActiveRecord; use yii\web\ServerErrorHttpException; /** * UpdateAction implements the API endpoint for updating a model. * * For more details and usage information on UpdateAction, see the [guide article on rest controllers](guide:rest-controllers). * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class UpdateAction extends Action { /** * @var string the scenario to be assigned to the model before it is validated and updated. */ public $scenario = Model::SCENARIO_DEFAULT; /** * Updates an existing model. * @param string $id the primary key of the model. * @return \yii\db\ActiveRecordInterface the model being updated * @throws ServerErrorHttpException if there is any error when updating the model */ public function run($id) { /* @var $model ActiveRecord */ $model = $this->findModel($id); if ($this->checkAccess) { call_user_func($this->checkAccess, $this->id, $model); } $model->scenario = $this->scenario; $model->load(Yii::$app->getRequest()->getBodyParams(), ''); if ($model->save() === false) { if ($model->hasErrors()) { $response = Yii::$app->getResponse(); $response->setStatusCode(422, 'Data Validation Failed.'); foreach ($model->getFirstErrors() as $message) { $firstErrors = $message; } return ['code' => 20004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20004'), ['firstErrors' => $firstErrors]))]; } elseif (!$model->hasErrors()) { throw new ServerErrorHttpException('Failed to update the object for unknown reason.'); } } return ['code' => 10000, 'message' => Yii::t('app', '10004'), 'data' => $model]; } }
90、在 Postman 中,PUT http://www.cmcp-api.localhost/v1/pages/4 ,200响应,如图90
{ "code": 10000, "message": "更新页面成功", "data": { "id": 4, "slug": "slug-20180110-44", "title": "title-20180110-44", "body": "body-20180110-44", "view": "view-20180110-44", "status": "1", "created_at": 1515554512, "updated_at": 1515569633, "_links": { "self": { "href": "http://www.cmcp-api.localhost/v1/pages/4" }, "index": { "href": "http://www.cmcp-api.localhost/v1/pages" }, "view": { "href": "http://www.cmcp-api.localhost/v1/pages/4" }, "create": { "href": "http://www.cmcp-api.localhost/v1/pages" }, "update": { "href": "http://www.cmcp-api.localhost/v1/pages/4" }, "delete": { "href": "http://www.cmcp-api.localhost/v1/pages/4" }, "options": { "href": "http://www.cmcp-api.localhost/v1/pages" } } } }
91、在 Postman 中,PUT http://www.cmcp-api.localhost/v1/pages/4 ,422响应,如图91
注:
Accept application/json; version=0.0
Accept-Language en-US
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded
Body
slug 的值已经被另一页面占用
{ "code": 20004, "message": "Data validation failed: Slug \"slug-20180110-5\" has already been taken." }
92、DELETE /pages/4: 删除页面4,编辑 \api\rests\page\DeleteAction.php,如图92
<?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * @license http://www.yiiframework.com/license/ */ namespace api\rests\page; use Yii; use yii\web\ServerErrorHttpException; /** * DeleteAction implements the API endpoint for deleting a model. * * For more details and usage information on DeleteAction, see the [guide article on rest controllers](guide:rest-controllers). * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class DeleteAction extends Action { /** * Deletes a model. * @param mixed $id id of the model to be deleted. * @throws ServerErrorHttpException on failure. */ public function run($id) { $model = $this->findModel($id); if ($this->checkAccess) { call_user_func($this->checkAccess, $this->id, $model); } if ($model->delete() === false) { throw new ServerErrorHttpException('Failed to delete the object for unknown reason.'); } return ['code' => 10000, 'message' => Yii::t('app', '10005')]; } }
93、在 Postman 中,DELETE http://www.cmcp-api.localhost/v1/pages/7 ,200响应,如图93
注:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br
{ "code": 10000, "message": "删除页面成功" }
94、展示一下语言包文件的最后内容,如图94
\api\messages\zh\app.php
return [ 10000 => 'success', 10001 => '获取页面列表成功', 10002 => '获取页面详情成功', 10003 => '创建页面成功', 10004 => '更新页面成功', 10005 => '删除页面成功', ];
\api\messages\zh\error.php
return [ 20000 => 'error', 20001 => '页面列表为空', 20002 => '页面ID:{id},不存在', 20003 => '页面ID:{id},的状态不活跃', 20004 => '数据验证失败:{firstErrors}', ];
\api\messages\en\app.php
return [ 10000 => 'success', 10001 => 'Get page list is successful', 10002 => 'Get page details succeeded', 10003 => 'Create a page success', 10004 => 'Update page success', 10005 => 'Delete page success', ];
\api\messages\en\error.php
return [ 20000 => 'error', 20001 => 'Page list is empty', 20002 => 'Page ID: {id}, does not exist', 20003 => 'Page ID: {id}, the status is not active', 20004 => 'Data validation failed: {firstErrors}', ];
95、OPTIONS /pages: 显示关于末端 /pages 支持的动词,在 Postman 中,OPTIONS http://www.cmcp-api.localhost/v1/pages ,200响应,如图95
96、OPTIONS /pages/1: 显示关于末端 /pages/1 支持的动词,在 Postman 中,OPTIONS http://www.cmcp-api.localhost/v1/pages/1 ,200响应,如图96
97、总结:现在支持的行为:index、view、create、update、delete、options,除 options 之外,基本上是继承之后,再次覆写实现具体的需求了的。
1 条回复
[…] […]