基于 yiisoft/yii2-app-advanced,在 GitHub 上新建仓库 yii2-app-advanced,新建接口应用(实现 RESTful 风格的 Web Service 服务的 API),实现 RESTful Web 服务,支持国际化(动态地设置目标语言,默认为简体中文) (三)

1、RESTful Web 服务,建议基于一个单独的接口应用来实现,此时基于 api 应用来实现

2、新建目录:\api\rests,此目录将做为 RESTful Web 服务的操作方法类目录

3、新建控制器类 \api\controllers\UserController.php ,控制器类扩展自 [[yii\rest\ActiveController]]。数据序列化的实现,在响应主体内包含分页信息来简化客户端的开发工作, 如图1

图1

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 15:35
 */
namespace api\controllers;

use yii\rest\ActiveController;

class UserController extends ActiveController
{
    public $serializer = [
        'class' => 'api\rests\user\Serializer',
        'collectionEnvelope' => 'items',
    ];

    /**
     * @inheritdoc
     */    public function actions()
    {
        return [
            'index' => [
                'class' => 'api\rests\user\IndexAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
            ],
            'view' => [
                'class' => 'api\rests\user\ViewAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
            ],
            'create' => [
                'class' => 'api\rests\user\CreateAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
                'scenario' => $this->createScenario,
            ],
            'update' => [
                'class' => 'api\rests\user\UpdateAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
                'scenario' => $this->updateScenario,
            ],
            'delete' => [
                'class' => 'api\rests\user\DeleteAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
            ],
            'options' => [
                'class' => 'api\rests\user\OptionsAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
            ],
        ];
    }
}

注:如果仅支持较少的行为,可以选择下面的方案,例

    public function actions()
    {
        $actions = parent::actions();
        
        $actions['view']['class'] = 'api\rests\user\ViewAction';

        return $actions;
    }

4、当配置的内容十分复杂,通用做法是将其存储在一或多个 PHP 文件中, 这些文件被称为配置文件。一个配置文件返回的是 PHP 数组。版本化的实现,配置URL规则,修改有关在应用程序配置的urlManager组件的配置,支持 v1 模块,支持所有行为,新建:\api\config\urlManager.php,如图2

图2

<?php
return [
    'class' => yii\web\UrlManager::class,
    'enablePrettyUrl' => true,
    'enableStrictParsing' => true,
    'showScriptName' => false,
    'rules' => [
        [
            'class' => 'yii\rest\UrlRule',
            'controller' => ['v1/user'],
        ],
    ],
];

5、将 \api\config\urlManager.php 包含在 \api\config\main.php 里,编辑 \api\config\main.php

        'urlManager' => require __DIR__ . '/urlManager.php',

6、把每个主要版本的 API 实现在一个单独的模块 ID 的主版本号,基于 Gii 生成模块 v1,打开网址:http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/module ,删除目录:\api\modules\v1\views,如图3

图3

7、新建 \api\modules\v1\models\User.php,继承至 \api\models\User.php,如图4
注:\api\modules\v1\models\User(仅用于 v1 模块) > \api\models\User(仅用于 api 应用) > \common\logics\User.php(可用于 api、frontend 等多个应用) > \common\models\User.php(仅限于 Gii 生成) > \yii\db\ActiveRecord

图4

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 16:04
 */
namespace api\modules\v1\models;


class User extends \api\models\User
{

}

8、\api\modules\v1\controllers\DefaultController.php 重命名为 \api\modules\v1\controllers\UserController.php,通过指定 [[yii\rest\ActiveController::modelClass|modelClass]] 作为 api\modules\v1\models\User, 控制器就能知道使用哪个模型去获取和处理数据。编辑代码,如图5
注:\api\modules\v1\controllers\UserController.php(仅用于 v1 模块) > \api\controllers\UserController.php(仅用于 api 应用) > \yii\rest\ActiveController

图5

<?php

namespace api\modules\v1\controllers;

/**
 * User controller for the `v1` module
 */class UserController extends \api\controllers\UserController
{
    public $modelClass = 'api\modules\v1\models\User';
}

9、要在应用中使用模块,只需要将模块加入到应用主体配置的[[yii\base\Application::modules|modules]]属性的列表中, 如下代码的应用主体配置 使用 v1 模块,编辑 \api\config\main.php,如图6

图6

    'modules' => [
        'v1' => [
            'class' => api\modules\v1\Module::class,
        ],
    ],

10、在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1 ,404响应,格式为HMTL,如图7

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-param" content="_csrf-api">
        <meta name="csrf-token" content="00ssponpmJwoWBZLFTExhai8kSgeTCkNzKenlyRbznyiAHzyz6Pc9XICYxpmWV7KzePdQnYZG2umkezQXg38Tw==">
        <title>Not Found (#404)</title>
        <link href="/assets/73866dfd/css/bootstrap.css" rel="stylesheet">
        <link href="/css/site.css" rel="stylesheet">
    </head>
    <body>
    </body>
</html>

11、对于404响应格式为HTML的解决,编辑 \api\config\main.php,设置默认的响应格式为JSON

        'response' => [
            'format' => yii\web\Response::FORMAT_JSON,
        ],

12、在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1 ,404响应,格式为JSON,如图8

图8

{
    "name": "Not Found",
    "message": "用户未找到。",
    "code": 0,
    "status": 404,
    "type": "yii\\web\\NotFoundHttpException"
}

13、RESTful APIs 通常是无状态的,因此,配置 user 应用组件,编辑 \api\config\main.php
注:配置user 应用组件:
设置 [[yii\web\User::enableSession|enableSession]] 属性为 false.
设置 [[yii\web\User::loginUrl|loginUrl]] 属性为null 显示一个HTTP 403 错误而不是跳转到登录界面.

        'user' => [
            'identityClass' => 'api\models\User',
            'enableSession' => false,
            'loginUrl' => null,
            'enableAutoLogin' => false,
        ],

14、复制目录 \vendor\yiisoft\yii2\rest 下的 Action.php、IndexAction.php、OptionsAction.php、ViewAction.php、CreateAction.php、UpdateAction.php、DeleteAction.php、Serializer.php 至目录 \api\rests\user,如果为多个单词组合的目录,建议目录使用小写+下划线,参考网址:https://github.com/hfcorriez/fig-standards/blob/master/accepted/zh_CN/PSR-0.md

15、编辑 \api\rests\user\IndexAction.php,调整命名空间、继承关系、查询条件等

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
namespace api\rests\user;

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_DELETED = 0; //状态:已删除
    const STATUS_ACTIVE = 10; //状态:活跃

    /**
     * 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,
            ],
        ]);
    }
}

16、编辑 \api\rests\user\Serializer.php,调整命名空间、继承关系、响应结构(响应成功:”code”: 10000,”message”,”data”;响应失败:”code”: 不等于10000的其他数字,”message”)等

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
namespace api\rests\user;

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('success', '10001'), 'data' => array_merge($result, $this->serializePagination($pagination))];
        }

        return ['code' => 10000, 'message' => Yii::t('success', '10001'), 'data' => $result];
    }
}

17、编辑 \api\config\main.php,配置接口应用的 i18n 应用组件

        'i18n' => [
            'translations' => [
                'model/*'=> [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'forceTranslation' => true,
                    'basePath'=>'@common/messages',
                    'fileMap'=>[
                    ],
                ],
                '*'=> [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'forceTranslation' => true,
                    'basePath'=>'@api/messages',
                    'fileMap'=>[
                    ],
                ],
            ],
        ],

18、新建语言包文件:\api\messages\zh-CN\success.php(简体中文、响应成功)

<?php
return [
    10000 => 'success',
    10001 => '获取用户列表成功',
    10002 => '获取用户详情成功',
    10003 => '创建用户成功',
    10004 => '更新用户成功',
    10005 => '删除用户成功',
];

19、新建语言包文件:\api\messages\zh-CN\error.php(简体中文、响应失败)

<?php
return [
    20000 => 'error',
    20001 => '用户列表为空',
    20002 => '用户ID:{id},不存在',
    20003 => '用户ID:{id},的状态为已删除',
    20004 => '数据验证失败:{firstErrors}',
];

20、新建语言包文件:\api\messages\en-US\success.php(英语美国、响应成功)

<?php
return [
    10000 => 'success',
    10001 => 'Get user list success',
    10002 => 'Get user details success',
    10003 => 'Create user success',
    10004 => 'Update user success',
    10005 => 'Delete user success',
];

21、新建语言包文件:\api\messages\en-US\error.php(英语美国、响应失败)

<?php
return [
    20000 => 'error',
    20001 => 'User list is empty',
    20002 => 'User ID: {id}, does not exist',
    20003 => 'User ID: {id}, the status is not active',
    20004 => 'Data validation failed: {firstErrors}',
];

22、ContentNegotiator支持响应内容格式处理和语言处理。 通过检查 GET 参数和 Accept HTTP头部来决定响应内容格式和语言。配置ContentNegotiator支持英语美国和简体中文。编辑 \common\config\main.php

注:如果请求中没有检测到语言, 使用 [[languages]] 第一个配置项。

    'bootstrap' => ['contentNegotiator'],
    'components' => [

        'contentNegotiator' => [
            'class' => 'yii\filters\ContentNegotiator',
            'languages' => [
                'zh-CN',
                'en-US',
            ],
        ],

    ],

删除

    'sourceLanguage' => 'en-US',
    'language' => 'zh-CN',

23、在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,200响应,message默认为简体中文,如图9
注:
Accept application/json; version=0.0

图9

{
    "code": 10000,
    "message": "获取用户列表成功",
    "data": {
        "items": [
            {
                "id": 1,
                "username": "111111",
                "auth_key": "P66wcJVGQL2toEpYr-Kc3z_rk7jJHMip",
                "password_hash": "$2y$13$qWV4xv1YLBtyn5cVx106VO6kTUOUP/7kX3oaC6CVpczHobkUrc6AG",
                "password_reset_token": null,
                "email": "111111@163.com",
                "status": 10,
                "created_at": 1522821105,
                "updated_at": 1522821105
            },
            {
                "id": 2,
                "username": "222222",
                "auth_key": "MgGfnpw6mYtkzctYkTOscyJAY_xxu739",
                "password_hash": "$2y$13$Xe0I4yjXL1FLvue8Q1YEU.nX8wH9HZzBOHSdbx2FL49me15CqoSa2",
                "password_reset_token": null,
                "email": "222222@163.com",
                "status": 10,
                "created_at": 1522821319,
                "updated_at": 1522821319
            },
            {
                "id": 3,
                "username": "333333",
                "auth_key": "WPeS81Lh4EUg0dp8FQ7ely4MF1ucF-ph",
                "password_hash": "$2y$13$.2G63zy8q2wTH/dWf.gLVe7c8kXKw3YCT5gquVhNhpTS2Albro1Ua",
                "password_reset_token": null,
                "email": "333333@163.com",
                "status": 10,
                "created_at": 1522823787,
                "updated_at": 1522823787
            },
            {
                "id": 4,
                "username": "444444",
                "auth_key": "scn4skhTfkg9YvRH1q9JZHkkddSR9H6a",
                "password_hash": "$2y$13$0RNx33MZIwC.FlSzMB0fuOCJAKmb1TlZzWCAujaCY/ixiM11myQ0a",
                "password_reset_token": null,
                "email": "444444@163.com",
                "status": 10,
                "created_at": 1522823915,
                "updated_at": 1522823915
            }
        ],
        "_links": {
            "self": {
                "href": "http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users?user=1"
            }
        },
        "_meta": {
            "totalCount": 4,
            "userCount": 1,
            "currentuser": 1,
            "peruser": 20
        }
    }
}

24、在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,200响应,message为简体中文,如图10
注:
Accept application/json; version=0.0
Accept-Language zh-CN

图10

{
    "code": 10000,
    "message": "获取用户列表成功",
    "data": {
        "items": [
            {
                "id": 1,
                "username": "111111",
                "auth_key": "P66wcJVGQL2toEpYr-Kc3z_rk7jJHMip",
                "password_hash": "$2y$13$qWV4xv1YLBtyn5cVx106VO6kTUOUP/7kX3oaC6CVpczHobkUrc6AG",
                "password_reset_token": null,
                "email": "111111@163.com",
                "status": 10,
                "created_at": 1522821105,
                "updated_at": 1522821105
            },
            {
                "id": 2,
                "username": "222222",
                "auth_key": "MgGfnpw6mYtkzctYkTOscyJAY_xxu739",
                "password_hash": "$2y$13$Xe0I4yjXL1FLvue8Q1YEU.nX8wH9HZzBOHSdbx2FL49me15CqoSa2",
                "password_reset_token": null,
                "email": "222222@163.com",
                "status": 10,
                "created_at": 1522821319,
                "updated_at": 1522821319
            },
            {
                "id": 3,
                "username": "333333",
                "auth_key": "WPeS81Lh4EUg0dp8FQ7ely4MF1ucF-ph",
                "password_hash": "$2y$13$.2G63zy8q2wTH/dWf.gLVe7c8kXKw3YCT5gquVhNhpTS2Albro1Ua",
                "password_reset_token": null,
                "email": "333333@163.com",
                "status": 10,
                "created_at": 1522823787,
                "updated_at": 1522823787
            },
            {
                "id": 4,
                "username": "444444",
                "auth_key": "scn4skhTfkg9YvRH1q9JZHkkddSR9H6a",
                "password_hash": "$2y$13$0RNx33MZIwC.FlSzMB0fuOCJAKmb1TlZzWCAujaCY/ixiM11myQ0a",
                "password_reset_token": null,
                "email": "444444@163.com",
                "status": 10,
                "created_at": 1522823915,
                "updated_at": 1522823915
            }
        ],
        "_links": {
            "self": {
                "href": "http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users?user=1"
            }
        },
        "_meta": {
            "totalCount": 4,
            "userCount": 1,
            "currentuser": 1,
            "peruser": 20
        }
    }
}

25、GET /users/1: 返回用户 1 的详细信息,编辑 \api\rests\user\Action.php,调整命名空间、继承关系、响应结构等

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
namespace api\rests\user;

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);
    }
}

26、编辑 \api\rests\user\ViewAction.php,调整命名空间、继承关系、响应结构等

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
namespace api\rests\user;

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_DELETED = 0; //状态:已删除
    const STATUS_ACTIVE = 10; //状态:活跃

    /**
     * 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_DELETED) {
            return ['code' => 20003, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20003'), ['id' => $id]))];
        }

        return ['code' => 10000, 'message' => Yii::t('success', '10002'), 'data' => $model];
    }
}

27、在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/1 ,200响应,其状态为活跃,如图11
注:
Accept application/json; version=0.0
Accept-Language zh-CN

图11

{
    "code": 10000,
    "message": "获取用户详情成功",
    "data": {
        "id": 1,
        "username": "111111",
        "auth_key": "P66wcJVGQL2toEpYr-Kc3z_rk7jJHMip",
        "password_hash": "$2y$13$qWV4xv1YLBtyn5cVx106VO6kTUOUP/7kX3oaC6CVpczHobkUrc6AG",
        "password_reset_token": null,
        "email": "111111@163.com",
        "status": 10,
        "created_at": 1522821105,
        "updated_at": 1522821105
    }
}

28、在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/5 ,404响应,用户不存在,如图12
注:
Accept application/json; version=0.0
Accept-Language en-US

图12

{
    "name": "Not Found",
    "message": "User ID: 5, does not exist",
    "code": 20002,
    "status": 404,
    "type": "yii\\web\\NotFoundHttpException"
}

29、POST /users: 创建一个新用户,复制 \api\models\SignupForm.php 至 \api\models\Signup.php

<?php
namespace api\models;

use yii\base\Model;

/**
 * Signup
 */class Signup extends Model
{
    public $username;
    public $email;
    public $password;


    /**
     * {@inheritdoc}
     */    public function rules()
    {
        return [
            ['username', 'trim'],
            ['username', 'required'],
            ['username', 'unique', 'targetClass' => '\api\models\User'],
            ['username', 'string', 'min' => 2, 'max' => 255],
            ['email', 'trim'],
            ['email', 'required'],
            ['email', 'email'],
            ['email', 'string', 'max' => 255],
            ['email', 'unique', 'targetClass' => '\api\models\User'],
            ['password', 'required'],
            ['password', 'string', 'min' => 6],
        ];
    }
}

30、新建 \api\modules\v1\models\Signup.php,继承至 \api\models\Signup.php

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/08
 * Time: 10:48
 */
namespace api\modules\v1\models;


class Signup extends \api\models\Signup
{

}

31、POST /users: 创建一个新用户,编辑 \api\rests\user\CreateAction.php

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
namespace api\rests\user;

use Yii;
use yii\base\Model;
use api\models\Signup;
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);
        }

        $signup = new Signup();
        $signup->load(Yii::$app->getRequest()->getBodyParams(), '');
        if (!$signup->validate()) {
            if ($signup->hasErrors()) {
                $response = Yii::$app->getResponse();
                $response->setStatusCode(422, 'Data Validation Failed.');
                foreach ($signup->getFirstErrors() as $message) {
                    $firstErrors = $message;
                    break;
                }
                return ['code' => 20004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20004'), ['firstErrors' => $firstErrors]))];
            } elseif (!$signup->hasErrors()) {
                throw new ServerErrorHttpException('Failed to create the object for unknown reason.');
            }
        }

        /* @var $model \yii\db\ActiveRecord */        $model = new $this->modelClass([
            'scenario' => $this->scenario,
        ]);

        $model->username = $signup->username;
        $model->email = $signup->email;
        $model->setPassword($signup->password);
        $model->generateAuthKey();
        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;
                break;
            }
            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('success', '10003'), 'data' => $model];
    }
}

32、在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,201响应,如图13
注:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded

图13

{
    "code": 10000,
    "message": "创建用户成功",
    "data": {
        "username": "555555",
        "email": "555555@163.com",
        "password_hash": "$2y$13$JcBRxuLFtLc4FfACRGwKpeXRNfhuP8XRGR/0ORvpXc8ob3c3D2E/q",
        "auth_key": "0eAriaA02Qw9J3s8ngwLtGlW34tpZ98k",
        "status": 10,
        "created_at": 1523156052,
        "updated_at": 1523156052,
        "id": 5
    }
}

33、在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,参数保持原样,422响应,如图14
注:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded

图14

{
    "code": 20004,
    "message": "数据验证失败:Username的值\"555555\"已经被占用了。"
}

34、PUT /users/5: 更新一个用户,新建 \api\models\UserUpdate.php

<?php
namespace api\models;

use yii\base\Model;

/**
 * UserUpdate
 */class UserUpdate extends Model
{
    public $id;
    public $email;
    public $password;
    public $status;

    const STATUS_DELETED = 0; //状态:已删除
    const STATUS_ACTIVE = 10; //状态:活跃

    /**
     * {@inheritdoc}
     */    public function rules()
    {
        return [
            ['email', 'trim'],
            ['email', 'required'],
            ['email', 'email'],
            ['email', 'string', 'max' => 255],
            ['email', 'unique', 'targetClass' => '\api\models\User', 'filter' => ['!=', 'id', $this->id]],
            ['password', 'required'],
            ['password', 'string', 'min' => 6],
            [['status'], 'in', 'range' => [self::STATUS_DELETED, self::STATUS_ACTIVE]],
        ];
    }
}

35、新建 \api\modules\v1\models\UserUpdate.php,继承至 \api\models\UserUpdate.php

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/08
 * Time: 11:19
 */
namespace api\modules\v1\models;


class UserUpdate extends \api\models\UserUpdate
{

}

36、PUT /users/4: 更新一个用户,编辑 \api\rests\user\UpdateAction.php

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
namespace api\rests\user;

use Yii;
use yii\base\Model;
use yii\db\ActiveRecord;
use api\models\UserUpdate;
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);
        }

        $userUpdate = new UserUpdate();
        $userUpdate->id = $id;
        $userUpdate->load(Yii::$app->getRequest()->getBodyParams(), '');
        if (!$userUpdate->validate()) {
            if ($userUpdate->hasErrors()) {
                $response = Yii::$app->getResponse();
                $response->setStatusCode(422, 'Data Validation Failed.');
                foreach ($userUpdate->getFirstErrors() as $message) {
                    $firstErrors = $message;
                    break;
                }
                return ['code' => 20004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20004'), ['firstErrors' => $firstErrors]))];
            } elseif (!$userUpdate->hasErrors()) {
                throw new ServerErrorHttpException('Failed to create the object for unknown reason.');
            }
        }

        $model->scenario = $this->scenario;
        $model->email = $userUpdate->email;
        $model->status = $userUpdate->status;
        $model->setPassword($userUpdate->password);
        $model->generateAuthKey();
        if ($model->save() === false) {
            if ($model->hasErrors()) {
                $response = Yii::$app->getResponse();
                $response->setStatusCode(422, 'Data Validation Failed.');
                foreach ($model->getFirstErrors() as $message) {
                    $firstErrors = $message;
                    break;
                }
                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('success', '10004'), 'data' => $model];
    }
}

37、在 Postman 中,PUT http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/5 ,200响应,如图15

图15

{
    "code": 10000,
    "message": "更新用户成功",
    "data": {
        "id": 5,
        "username": "555555",
        "auth_key": "nXryM-o_ky4TAdn758l47pQhLhsSnjSZ",
        "password_hash": "$2y$13$qB5Y3Zl7B8NyPDFMXc06ye8OAhybCpp7p6wToyhtLH2iDzhd06ts.",
        "password_reset_token": null,
        "email": "555555@qq.com",
        "status": "0",
        "created_at": 1523156052,
        "updated_at": 1523165956
    }
}

38、在 Postman 中,PUT http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/4 ,422响应,如图16

注:
Accept application/json; version=0.0
Accept-Language en-US
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded

Body
email 的值已经被另一用户占用(如果值未发生变化,则可验证通过)

图16

{
    "code": 20004,
    "message": "Data validation failed: Email \"444444@163.com\" has already been taken."
}

39、DELETE /users/5: 删除用户5,编辑 \api\rests\user\DeleteAction.php

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
namespace api\rests\user;

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('success', '10005')];
    }
}

40、在 Postman 中,DELETE http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/5 ,200响应,如图17
注:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br

图17

{
    "code": 10000,
    "message": "删除用户成功"
}

41、编辑 \api\rests\user\OptionsAction.php

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
namespace api\rests\user;

use Yii;

/**
 * OptionsAction responds to the OPTIONS request by sending back an `Allow` header.
 *
 * For more details and usage information on OptionsAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */class OptionsAction extends \yii\rest\Action
{
    /**
     * @var array the HTTP verbs that are supported by the collection URL
     */    public $collectionOptions = ['GET', 'POST', 'HEAD', 'OPTIONS'];
    /**
     * @var array the HTTP verbs that are supported by the resource URL
     */    public $resourceOptions = ['GET', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];


    /**
     * Responds to the OPTIONS request.
     * @param string $id
     */    public function run($id = null)
    {
        if (Yii::$app->getRequest()->getMethod() !== 'OPTIONS') {
            Yii::$app->getResponse()->setStatusCode(405);
        }
        $options = $id === null ? $this->collectionOptions : $this->resourceOptions;
        $headers = Yii::$app->getResponse()->getHeaders();
        $headers->set('Allow', implode(', ', $options));
        $headers->set('Access-Control-Allow-Methods', implode(', ', $options));
    }
}

42、OPTIONS /users: 显示关于末端 /users 支持的动词,在 Postman 中,OPTIONS http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,200响应,如图18

图18

43、OPTIONS /users/4: 显示关于末端 /users/4 支持的动词,在 Postman 中,OPTIONS http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/1 ,200响应,如图19

图19

44、由于[[yii\web\Response::format|response format]] 响应格式不为html, 因此,不会使用错误或异常视图来显示错误信息,可取消错误动作,编辑 \api\config\main.php

删除

        'errorHandler' => [
            'errorAction' => 'site/error',
        ],

45、总结:现在支持的行为:index、view、create、update、delete、options,皆是继承之后,再次覆写实现具体的需求。

 

永夜