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 之外,基本上是继承之后,再次覆写实现具体的需求了的。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/250389.html
