Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new interface application (implement RESTful-style web service services. API), implement RESTful web service, support internationalization (drivingly set the target language, default to simplified Chinese)

在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,200响应,message为简体中文

Yii2 Advanced in Action Series: RESTful API + RPC Services + Internationalization + Logging & Testing

新建 api 目录、配置和环境、测试、Vagrant 等的支持。

(1) Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new interface application (implement RESTful-style web service services. API), support for creating new API directories, configuration and environment, testing, vagrant, etc.

(2) Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new interface application (implement RESTful-style web service services. API), implement model hierarchy: data layer, logic layer, clarify common directory, application, module inheritance, reference relationship

在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,200响应,message为简体中文

(3) Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new interface application (implement RESTful-style web service services. API), implement RESTful web service, support internationalization (drivingly set the target language, default to simplified Chinese)

运行测试,获取详细的输出,可看到一步一步的行为报告,符合预期

(4) Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new interface application (implement RESTful-style web service services. API), prepare some examples of automated tests (API tests) for user-related operations in the tests directory of the API to ensure that the application will not affect existing functions when changing or adding new functions

在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?filter[level]=4&filter[category][like]=api\behaviors\RequestLogBehavior&filter[prefix][like]=app-api&filter[log_time][gte]=1528090828&filter[log_time][lte]=1529564924.6648 ,200响应,空数组已经被自动转换为空对象,以保证字段格式的统一,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs/3

(5) Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new interface application (implement RESTful-style web service services. API), adjust the default character set to: utf8mb4, the adjustment of the interface response format, the empty array is automatically converted to empty objects, and the request log message is collected in the interface application (1 request corresponds to 1 log message) to the database, and the corresponding interface to implement the log function: log list (set data filter to enable filter processing), log details

在开发环境中,执行数据库迁移命令失败:1071 Specified key was too long; max key length is 767 bytes

(6) Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new interface application (implement RESTful-style web service services. API), adjust the default character set to: utf8mb4, the adjustment of the interface response format, the empty array is automatically converted to empty objects, and the request log message is collected in the interface application (1 request corresponds to 1 log message) to the database, and the corresponding interface to implement the log function: log list (set data filter to enable filter processing), log details (2)

浏览 user 表数据,status、updated_at 已经更新

(7) Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new interface application (implement RESTful-style web service services. API), realize the soft deletion of ActiveRecord, generate ActiveQuery, and customize the query class

执行数据库迁移命令,查看数据库表 user 结构,符合预期

(8) Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new interface application (implement RESTful-style web service services. API), realize the soft deletion of ActiveRecord, generate ActiveQuery, and customize the query class (2)

在 Postman 中 POST 请求,http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,输入格式:application/json

(9) Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new interface application (implement RESTful-style web service services. API), application/x-www-form-urlencoded and multipart/form-data input formats, default support, new application/json input format support

打开 Windows PowerShell,执行 init 命令并选择 dev 作为环境,api应用所需环境配置文件自动生成

(10) Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new remote procedure call application (implement hprose 2.0 for PHP’s RPC server), support for creating new RPC directories, configuration and environment, testing, Vagrant, etc.

出现以上原因是因为 Windows 下 nginx php 环境,不支持并发的原因,当同时访问多个域名,并且同时指向你本地服务的时候,就不支持并发了。nginx.conf 里面 对不同 server 修改 fastcgi_pass 的端口号,启动多个 php-cgi。

(11) Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new remote procedure call application (implement hprose 2.0 for PHP RPC server), implement the corresponding RPC service of the page in the RPC directory, and implement the RPC client in the API directory

执行数据库迁移,查看表结构,基于 Gii 重新生成模型文件

(12) Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, the implementation of uuid

由于日志消息是存储于 MySQL 的 log 表中,在生产环境中,累积了大量的数据,log 表的数据长度已经达到 数百 GB ,因此,决定基于控制台命令行实现日志消息的定时删除,开发环境的 log 表的数据长度为 14.64 GB

(13) Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, automatic timing deletion of log messages

1. RESTful web service, it is recommended to implement based on a separate interface application, at this time, it is implemented based on the API application 2. Create a new directory: \api\rests, this directory will be used as the operation method category directory of the RESTful web service 3. Create a new controller class \API\Controllers\UserController.php , and the controller class is extended from[[yii\rest\ActiveController]]. The realization of data serialization includes paging information in the response body to simplify the development of the client, as shown in Figure 1
新建控制器类 \api\controllers\UserController.php ,控制器类扩展自 [[yii\rest\ActiveController]]。数据序列化的实现,在响应主体内包含分页信息来简化客户端的开发工作
Figure 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'],
            ],
        ];
    }
}



Note: If only less behavior is supported, you can choose the following scheme, such as


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

        return $actions;
    }


4. When the content of the configuration is very complex, the common practice is to store it in one or more PHP files, which are called configuration files. A configuration file returns an array of php. Versioning of the versioned implementation, configuring the URL rules, modifying the configuration of the urlmanager component configured in the application, supporting the v1 module, supporting all behaviors, create a new:\api\config\urlmanager.php, as shown in Figure 2
当配置的内容十分复杂,通用做法是将其存储在一或多个 PHP 文件中, 这些文件被称为配置文件。一个配置文件返回的是 PHP 数组。版本化的实现,配置URL规则,修改有关在应用程序配置的urlManager组件的配置,支持 v1 模块,支持所有行为,新建:\api\config\urlManager.php
Figure 2


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



5. Include \api\config\urlmanager.php in \api\config\main.php, edit \api\config\main.php


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


6. Implement the API of each major version in the main version number of a separate module ID, and generate the module based on the GII v1, open the URL: http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/module , delete the directory: \api\modules\v1\views, as shown in Figure 3
把每个主要版本的 API 实现在一个单独的模块 ID 的主版本号,基于 Gii 生成模块 v1,打开网址:http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/module ,删除目录:\api\modules\v1\views
Figure 3
7. Create a new \API\modules\v1\models\user.php, inherit to \api\models\user.php, as shown in Figure 4 Note: \api\modules\v1\models\user (only for v1 modules) > \api\models\user (for API applications only) > \common\logics\user.php (available for multiple applications such as API, frontend, etc.) > \common\models\user.php (limited to GII generation only) > \yii\db\ActiveRecord
新建 \api\modules\v1\models\User.php,继承至 \api\models\User.php
Figure 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 Renamed \API\modules\v1\Controllers\UserController.php, by specifying[[yii\rest\ActiveController::modelClass|modelClass]] As API\modules\v1\models\user, the controller can know which model to use to obtain and process data. Edit the code, as shown in Figure 5 Note: \api\modules\v1\Controllers\UserController.php (only for the v1 module) > \API\Controllers\UserController.php (only for API Application) > \yii\rest\ActiveController
\api\modules\v1\controllers\DefaultController.php 重命名为 \api\modules\v1\controllers\UserController.php,通过指定 [[yii\rest\ActiveController::modelClass|modelClass]] 作为 api\modules\v1\models\User, 控制器就能知道使用哪个模型去获取和处理数据
Figure 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. To use the module in the application, you only need to add the module to the configuration of the application body.[[yii\base\Application::modules|modules]] in the list of attributes, the application body configuration of the following code Using the v1 module, edit \api\config\main.php, as shown in Figure 6
要在应用中使用模块,只需要将模块加入到应用主体配置的[[yii\base\Application::modules|modules]]属性的列表中, 如下代码的应用主体配置 使用 v1 模块,编辑 \api\config\main.php
Figure 6


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


10. In Postman, get http://api.github-shuijingwan-yii2-app-advanced.localhost/v1 , 404 response, the format is HMTL, as shown in Figure 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. For the solution of 404 response format is HTML, edit \api\config\main.php, and set the default response format to json


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


12. In Postman, get http://api.github-shuijingwan-yii2-app-advanced.localhost/v1 , 404 response, the format is JSON, as shown in Figure 8
在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1 ,404响应,格式为JSON
8


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


13. RESTful APIs are usually stateless, so configure the User application component, edit \API\config\main.php Note: Configure User Application Components: install[[yii\web\User::enableSession|enableSession]] property is false. install[[yii\web\User::loginUrl|loginUrl]] The attribute is null to display an HTTP 403 error instead of jumping to the login interface.


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


14. Copy the directory under \vendor\yiisoft\yii2\rest action.php, indexaction.php, optionsAction.php, viewaction.php, createaction.php, updateAction.php, deleteAction.php, serializer.php to directory \API\RESTS\User, if it is a directory with multiple words combined, it is recommended to use lowercase + underscore for the directory, refer to the URL: https://github. com/hfcorriez/fig-standards/blob/master/accepted/zh_cn/psr-0.md 15. Edit \API\RESTS\User\IndexAction.php, adjust namespace, inheritance relationship, query conditions, etc.


<?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. Edit \API\RESTS\User\Serializer.php, adjust the namespace, inheritance relationship, and response structure (response success: “code”: 10000,”message”,”data”; response failed: “code”: other numbers not equal to 10000, “message”) etc.


<?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. Edit \API\config\main.php to configure the I18n application component of the interface application


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


18. Create a new language pack file: \api\messages\zh-cn\success.php (Simplified Chinese, the response is successful)


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



19. Create a new language pack file: \api\messages\zh-cn\error.php (Simplified Chinese, response failed)


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



20. Create a new language pack file: \api\messages\en-us\success.php (English in the United States, the response is successful)


<?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. Create a new language pack file: \api\messages\en-us\error.php (English American, response failed)


<?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 supports response content format processing and language processing. Determine the response content format and language by checking the GET parameter and the Accept HTTP header. Configure ContentNegotiator to support English American and Simplified Chinese. Edit \common\config\main.php Note: If no language is detected in the request, use[[languages]] The first configuration item.


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

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

    ],


Delete


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


23. In Postman, get http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users , 200 response, the message defaults to simplified Chinese, as shown in Figure 9 Note: accept application/json; version=0.0
在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,200响应,message默认为简体中文
Figure 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. In Postman, get http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users , 200 response, the message is simplified Chinese, as shown in Figure 10 Note: accept application/json; version=0.0 accept-language en
在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,200响应,message为简体中文
Figure 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: return the details of user 1, edit \api\rests\user\action.php, adjust the namespace, inheritance, response structure, etc.


<?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. Edit \api\rests\user\viewaction.php, adjust namespace, inheritance relationship, response structure, etc.


<?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. In Postman, get http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/1 , 200 responses, its status is active, as shown in Figure 11 Note: accept application/json; version=0.0 accept-language en
在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/1 ,200响应,其状态为活跃
Figure 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. In Postman, get http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/5 , 404 response, the user does not exist, as shown in Figure 12 Note: accept application/json; version=0.0 accept-language en-us
在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/5 ,404响应,用户不存在
Figure 12


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


29, post /users: Create a new user, copy \api\models\signupform.php to \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. Create a new \API\modules\v1\models\signup.php, inherit to \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: create a new user, edit \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. In Postman, post http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users , 201 response, as shown in Figure 13 Note: accept application/json; version=0.0 accept-language en Accept-encoding gzip, deflate, br content-type application/x-www-form-urlencoded
在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,201响应
Figure 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. In Postman, post http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users , the parameters remain as they are, 422 responds, as shown in Figure 14 Note: accept application/json; version=0.0 accept-language en Accept-encoding gzip, deflate, br content-type application/x-www-form-urlencoded
在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,参数保持原样,422响应
Figure 14


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


34, put /users/5: update a user, create a new \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. Create a new \API\modules\v1\models\userupdate.php, inherit to \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: update a user, edit \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. In Postman, put http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/5 , 200 responses, as shown in Figure 15
在 Postman 中,PUT http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/5 ,200响应
Figure 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. In Postman, put http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/4 , 422 response, as shown in Figure 16 Note: accept application/json; version=0.0 accept-language en-us Accept-encoding gzip, deflate, br content-type application/x-www-form-urlencoded Body The value of the email has been occupied by another user (if the value has not changed, it can be verified to pass)
在 Postman 中,PUT http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/4 ,422响应
Figure 16


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


39, delete /users/5: delete user 5, edit \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. In Postman, delete http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/5 , 200 responses, as shown in Figure 17 Note: accept application/json; version=0.0 accept-language en Accept-encoding gzip, deflate, br
在 Postman 中,DELETE http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/5 ,200响应
Figure 17


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


41. Edit \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: Displays verbs about the end/users supported, in Postman, options http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users , 200 responses, as shown in Figure 18
OPTIONS /users: 显示关于末端 /users 支持的动词,在 Postman 中,OPTIONS http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,200响应
18
43. Options /Users/4: Display verbs about the end /users/4 support, in Postman, options http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/1 , 200 responses, as shown in Figure 19
OPTIONS /users/4: 显示关于末端 /users/4 支持的动词,在 Postman 中,OPTIONS http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/1 ,200响应
Figure 19
44. Due to[[yii\web\Response::format|response format]] The response format is not HTML, so it will not use error or exception views to display error messages, you can cancel the error action, edit \api\config\main.php Delete


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


45. Summary: The current supported behaviors: index, view, create, update, delete, and options, all of which are inherited, and then overwrite to achieve specific requirements.  
Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new interface application (implement RESTful-style web service services. API), implement model hierarchy: data layer, logic layer, clarify common directory, application, module inheritance, reference relationship Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new interface application (implement RESTful-style web service services. API), prepare some examples of automated tests (API tests) for user-related operations in the tests directory of the API to ensure that the application will not affect existing functions when changing or adding new functions

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.