没有不值得去解决的问题,也没有不值得去学习的技术!

How to Build a RESTful Web Service by Duplicating the Backend App in Yii 2 Starter Kit on Windows 10 and PHP 7.1.12

In Postman, send a PUT request to http://www.cmcp-api.localhost/v1/pages/4 , which returns a 200 response

1. In Windows PowerShell, navigate to the E:\wwwroot\cmcp-api directory, as shown in Figure 1

In Windows PowerShell, navigate to the E:\wwwroot\cmcp-api directory
Figure 1

2. Create a new api application by copying the backend directory to api, as shown in Figure 2

Create a new api application by copying the backend directory to api
Figure 2

3. Edit \.env.dist to add api-related configurations; this is the configuration file committed to Git version control, as shown in Figure 3

Plaintext

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 = 

Edit \.env.dist to add api-related configurations; this is the configuration file committed to Git version control
Figure 3

4. Edit \.env to add api-related configurations; this is the actual effective configuration file, as shown in Figure 4

Plaintext

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 = 

Edit \.env to add api-related configurations; this is the actual effective configuration file
Figure 4

5. Edit \common\config\bootstrap.php to configure the alias for the API application, as shown in Figure 5

PHP

Yii::setAlias('@api', realpath(__DIR__.'/../../api'));
Yii::setAlias('@apiUrl', env('API_HOST_INFO') . env('API_BASE_URL') );

Edit \common\config\bootstrap.php to configure the alias for the API application
Figure 5

6. Edit \common\config\base.php to configure the language pack file for the API application, as shown in Figure 6

PHP

'api'=>'api.php',

Edit \common\config\base.php to configure the language pack file for the API application
Figure 6

7. Create a Chinese language pack file for the API application by copying \common\messages\zh\backend.php to \common\messages\zh\api.php (if you need to support other languages, you can create it in the respective language directory), as shown in Figure 7

Create a Chinese language pack file for the API application by copying \common\messages\zh\backend.php to \common\messages\zh\api.php (if you need to support other languages, you can create it in the respective language directory)
Figure 7

8. Edit \common\config\base.php to configure URL parsing and generation for the API application, as shown in Figure 8

PHP

        'urlManagerApi' => \yii\helpers\ArrayHelper::merge(
            [
                'hostInfo' => env('API_HOST_INFO'),
                'baseUrl' => env('API_BASE_URL'),
            ],
            require(Yii::getAlias('@api/config/_urlManager.php'))
        ),

Edit \common\config\base.php to configure URL parsing and generation for the API application
Figure 8

9. Edit \console\controllers\AppController.php. A search in this file reveals 6 instances of backend; copy 3 of them accordingly as api, as shown in Figure 9

PHP

    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',
    ];

Edit \console\controllers\AppController.php. A search in this file reveals 6 instances of backend; copy 3 of them accordingly as api
Figure 9

10. Edit \autocompletion.php to add configurations related to the API application, as shown in Figure 10

PHP

 * @property yii\web\UrlManager $urlManagerApi UrlManager for api application.

Edit \autocompletion.php to add configurations related to the API application
Figure 10

11. In the api directory, search for backend\, backend/, => ‘backend’, and BACKEND with strict case matching, and replace them with api\, api/, => ‘api’, and API respectively, as shown in Figure 11

Plaintext
Replace backend\ with: api\
Replace backend/ with: api/
Replace => 'backend' with: => 'api'
Replace BACKEND with: => API

In the api directory, search for backend\, backend/, => 'backend', and BACKEND with strict case matching, and replace them with api\, api/, => 'api', and API respectively
Figure 11

12. The \tests directory also contains corresponding configurations related to the new API application. Hold off on this part for now and configure it when automated testing is needed. View \api\web\index-test.php, as shown in Figure 12

The \tests directory also contains corresponding configurations related to the new API application. Hold off on this part for now and configure it when automated testing is needed. View \api\web\index-test.php
Figure 12

13. In Windows PowerShell, navigate to the E:\wwwroot\cmcp-api directory and run the command again: php console/yii app/setup to set writable and executable permissions, as shown in Figure 13

In Windows PowerShell, navigate to the E:\wwwroot\cmcp-api directory and run the command again: php console/yii app/setup to set writable and executable permissions
Figure 13

14. Add a new virtual host in the Nginx configuration file and restart Nginx, as shown in Figure 14

Nginx

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

}

Add a new virtual host in the Nginx configuration file and restart Nginx
Figure 14

15. Add configurations to the hosts file, as shown in Figure 15

Plaintext
# Content management platform API
127.0.0.1 cmcp-api.localhost www.cmcp-api.localhost frontend.cmcp-api.localhost backend.cmcp-api.localhost storage.cmcp-api.localhost

Add configurations to the hosts file
Figure 15

16. Open http://www.cmcp-api.localhost. If the style files fail to load and return a 404 response, you can delete all subdirectories under \api\web\assets, as shown in Figure 16

Open http://www.cmcp-api.localhost. If the style files fail to load and return a 404 response, you can delete all subdirectories under \api\web\assets
Figure 16

17. Prepare to implement the page creation functionality based on the API, as shown in Figure 17

Prepare to implement the page creation functionality based on the API
Figure 17

18. Adjust the relevant structure of the Page model to implement model layering. Reference URL: https://www.shuijingwanwq.com/2017/08/15/1713/ , as shown in Figure 18

Adjust the relevant structure of the Page model to implement model layering. Reference URL: https://www.shuijingwanwq.com/2017/08/15/1713/
Figure 18

19. Open the URL: http://frontend.cmcp-api.localhost/gii , as shown in Figure 19

Open the URL: http://frontend.cmcp-api.localhost/gii
Figure 19

20. Generate the corresponding model based on the database table ca_page, as shown in Figure 20

Generate the corresponding model based on the database table ca_page
Figure 20

21. Create a logics directory in the common directory to serve as the location for the logic layer of MySQL models, as shown in Figure 21

Create a logics directory in the common directory to serve as the location for the logic layer of MySQL models
Figure 21

22. Copy \common\models\Page.php to \common\logics\Page.php, as shown in Figure 22

PHP
<?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'),
        ];
    }
}
Copy \common\models\Page.php to \common\logics\Page.php
Figure 22

23. Edit \common\logics\Page.php based on diff, as shown in Figure 23

Edit \common\logics\Page.php based on diff
Figure 23

24. The MySQL model file in the common/logics directory handles business logic and inherits from the \common\models\Page data layer, as shown in Figure 24

PHP
<?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],
        ];
    }
 
}
The MySQL model file in the common/logics directory handles business logic and inherits from the \common\models\Page data layer
Figure 24

25. Click the Start button under Model Generator to generate the Page model with the namespace common\models. At this point, internationalization support is required, so overwrite \common\models\Page.php, as shown in Figure 25

Click the Start button under Model Generator to generate the Page model with the namespace common\models. At this point, internationalization support is required, so overwrite \common\models\Page.php
Figure 25

26. Edit \common\config\base.php. When the source language and target language are the same, set whether to force message translation. The default is false; set it to true, as shown in Figure 26

PHP

                '*'=> [
                    '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']
                ],

Edit \common\config\base.php. When the source language and target language are the same, set whether to force message translation. The default is false; set it to true
Figure 26

27. Create \common\messages\en\model\page.php to support message translation when the target language is English (United States), as shown in Figure 27

PHP

return [
    'ID' => 'ID',
    'Slug' => 'Slug',
    'Title' => 'Title',
    'Body' => 'Body',
    'View' => 'Page View',
    'Status' => 'Active',
    'Created At' => 'Created At',
    'Updated At' => 'Updated At',
];

Create \common\messages\en\model\page.php to support message translation when the target language is English (United States)
Figure 27

28. Create \common\messages\zh\model\page.php to support message translation when the target language is Simplified Chinese, as shown in Figure 28

PHP

return [
    'ID' => 'ID',
    'Slug' => '别名',
    'Title' => '标题',
    'Body' => '内容',
    'View' => '页面浏览',
    'Status' => '活动',
    'Created At' => '创建时间',
    'Updated At' => '更新时间',
];

Create \common\messages\zh\model\page.php to support message translation when the target language is Simplified Chinese
Figure 28

29. Create \api\models\Page.php. The MySQL model file in the api/models directory handles business logic (specific to the api application only) and inherits from the \common\logics\Page logic layer, as shown in Figure 29

Create \api\models\Page.php. The MySQL model file in the api/models directory handles business logic (specific to the api application only) and inherits from the \common\logics\Page logic layer
Figure 29

30. Copy \api\models\Page.php to \frontend\models\Page.php and \backend\models\Page.php, adjusting them to their respective namespaces, as shown in Figure 30

Copy \api\models\Page.php to \frontend\models\Page.php and \backend\models\Page.php, adjusting them to their respective namespaces
Figure 30

31. In the api application, search for use common\models\Page; and replace it with: use api\models\Page;. Apply similar processing in the frontend and backend applications, as shown in Figure 31

In the api application, search for use common\models\Page; and replace it with: use api\models\Page;. Apply similar processing in the frontend and backend applications
Figure 31

32. When the locale is set to Simplified Chinese, open http://backend.cmcp-api.localhost/page/create , and the translation feature works, as shown in Figure 32

When the locale is set to Simplified Chinese, open http://backend.cmcp-api.localhost/page/create , and the translation feature works
Figure 32

33. Edit your profile to set the locale to English (United States), as shown in Figure 33

Edit your profile to set the locale to English (United States)
Figure 33

34. When the locale is set to English (United States), open http://backend.cmcp-api.localhost/page/create again, and the translation feature works, as shown in Figure 34

When the locale is set to English (United States), open http://backend.cmcp-api.localhost/page/create again, and the translation feature works
Figure 34

35. Start implementing the RESTful Web service. Reference URL: https://www.shuijingwanwq.com/2016/11/28/1457/ , as shown in Figure 35

Start implementing the RESTful Web service. Reference URL: https://www.shuijingwanwq.com/2016/11/28/1457/
Figure 35

36. Open the URL: http://backend.cmcp-api.localhost/article/create to create an article, as shown in Figure 36

Open the URL: http://backend.cmcp-api.localhost/article/create to create an article
Figure 36

37. In Postman, send a GET request to http://frontend.cmcp-api.localhost/api/v1/articles . This uses the built-in RESTful Web service, and the response result is shown in Figure 37

JSON

{
    "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
    }
}

In Postman, send a GET request to http://frontend.cmcp-api.localhost/api/v1/articles . This uses the built-in RESTful Web service, and the response result is shown
Figure 37

38. For RESTful Web services, it is recommended to implement them based on a standalone API application rather than an api module within the frontend application. This makes it more convenient to maintain your web application, as shown in Figure 38

For RESTful Web services, it is recommended to implement them based on a standalone API application rather than an api module within the frontend application. This makes it more convenient to maintain your web application
Figure 38

39. Create a new directory: \api\rests. This directory will serve as the directory for action method classes of the RESTful Web service, as shown in Figure 39

Create a new directory: \api\rests. This directory will serve as the directory for action method classes of the RESTful Web service
Figure 39

40. Edit the controller class \api\controllers\PageController.php . The controller class extends from [[yii\rest\ActiveController]]. By specifying [[yii\rest\ActiveController::modelClass|modelClass]] as api\models\Page, the controller knows which model to use for fetching and processing data. As shown in Figure 40

PHP

namespace api\controllers;

use yii\rest\ActiveController;

class PageController extends ActiveController
{
    public $modelClass = 'api\models\Page';
}

Edit the controller class \api\controllers\PageController.php . The controller class extends from [[yii\rest\ActiveController]]. By specifying [[yii\rest\ActiveController::modelClass|modelClass]] as api\models\Page, the controller knows which model to use for fetching and processing data.
Figure 40

41. Configure URL rules by modifying the configuration of the urlManager component in the application configuration. Edit: \api\config\_urlManager.php, as shown in Figure 41

PHP

return [
    'class' => yii\web\UrlManager::class,
    'enablePrettyUrl' => true,
    'showScriptName' => false,
    'rules' => [
        [
            'class' => 'yii\rest\UrlRule',
            'controller' => ['page'],
        ],
    ],
];

Configure URL rules by modifying the configuration of the urlManager component in the application configuration. Edit: \api\config\_urlManager.php
Figure 41

42. In Postman, send a GET request to http://www.cmcp-api.localhost/pages , which returns a 403 response, as shown in Figure 42

JSON

{
    "name": "Forbidden",
    "message": "Login Required",
    "code": 0,
    "status": 403,
    "type": "yii\\web\\ForbiddenHttpException"
}

In Postman, send a GET request to http://www.cmcp-api.localhost/pages , which returns a 403 response
Figure 42

43. Edit \api\config\web.php and comment out the attached behavior globalAccess, to be adjusted later (authentication, authorization), as shown in Figure 43

PHP

    /*
    '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'],
            ]
        ]
    ]
    */

Edit \api\config\web.php and comment out the attached behavior globalAccess, to be adjusted later (authentication, authorization)
Figure 43

44. In Postman, send a GET request to http://www.cmcp-api.localhost/pages , which returns a 200 response, as shown in Figure 44

JSON

[
    {
        "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
    }
]

In Postman, send a GET request to http://www.cmcp-api.localhost/pages , which returns a 200 response
Figure 44

45. The data returned after calling the API service uses a unified format: an HTTP status code of 20x indicates a successful call, while an HTTP status code of 4xx or 5xx indicates a failed call. Adjust the return data format of the 200 response to be consistent with the 403 response, containing at least: “message”, “code”. Create language pack files: \api\messages\zh\app.php (successful response), \api\messages\zh\error.php (failed response), as shown in Figure 45

PHP

return [
    10000 => 'success',
    10001 => '获取页面列表成功',
];

The data returned after calling the API service uses a unified format: an HTTP status code of 20x indicates a successful call, while an HTTP status code of 4xx or 5xx indicates a failed call. Adjust the return data format of the 200 response to be consistent with the 403 response, containing at least: "message", "code". Create language pack files: \api\messages\zh\app.php (successful response), \api\messages\zh\error.php (failed response)
Figure 45

46. For versioning implementation, reference URL: https://github.com/yiisoft/yii2/blob/master/docs/guide-zh-CN/rest-versioning.md . Edit \api\controllers\PageController.php and remove modelClass, as shown in Figure 46

PHP
<?php
 
namespace api\controllers;
 
use yii\rest\ActiveController;
 
class PageController extends ActiveController
{
}
For versioning implementation, reference URL: https://github.com/yiisoft/yii2/blob/master/docs/guide-zh-CN/rest-versioning.md . Edit \api\controllers\PageController.php and remove modelClass
Figure 46

47. Implement each major version of the API in a separate module ID representing the major version number. Generate module v1 based on Gii by opening the URL: http://frontend.cmcp-api.localhost/gii/module , as shown in Figure 47

Implement each major version of the API in a separate module ID representing the major version number. Generate module v1 based on Gii by opening the URL: http://frontend.cmcp-api.localhost/gii/module
Figure 47

48. Create \api\modules\v1\models\Page.php, which inherits from \api\models\Page.php, as shown in Figure 48
Note: \api\modules\v1\models\Page (used only for the v1 module) > \api\models\Page (used only for the api application) > \common\logics\Page.php (can be used across multiple applications like api, frontend, etc.) > \common\models\Page.php (limited to Gii generation) > \yii\db\ActiveRecord

PHP
<?php
 
namespace api\modules\v1\models;
 
class Page extends \api\models\Page
{
 
}
Create \api\modules\v1\models\Page.php, which inherits from \api\models\Page.php
Figure 48

49. Rename \api\modules\v1\controllers\DefaultController.php to \api\modules\v1\controllers\PageController.php and edit the code, as shown in Figure 49
Note: \api\modules\v1\controllers\PageController.php (used only for the v1 module) > \api\controllers\PageController.php (used only for the api application) > \yii\rest\ActiveController

PHP
<?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';
}
Rename \api\modules\v1\controllers\DefaultController.php to \api\modules\v1\controllers\PageController.php and edit the code
Figure 49

50. To use a module in an application, simply add the module to the list of the [[yii\base\Application::modules|modules]] property in the application configuration. The application configuration in the following code uses the v1 module. Edit \api\config\web.php, as shown in Figure 50

PHP

    'modules' => [
        'v1' => [
            'class' => api\modules\v1\Module::class,
        ],
        'i18n' => [
            'class' => api\modules\i18n\Module::class,
            'defaultRoute' => 'i18n-message/index'
        ]
    ],

To use a module in an application, simply add the module to the list of the [[yii\base\Application::modules|modules]] property in the application configuration. The application configuration in the following code uses the v1 module. Edit \api\config\web.php
Figure 50

51. Configure URL rules by modifying the configuration of the urlManager component in the application configuration to support the v1 module. Edit: \api\config\_urlManager.php, as shown in Figure 51
Note: By configuring the only option to explicitly list which actions are supported, v1/page only supports: ‘index’, ‘update’, ‘delete’, ‘options’

PHP
<?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'],
        ],
    ],
];
Configure URL rules by modifying the configuration of the urlManager component in the application configuration to support the v1 module. Edit: \api\config\_urlManager.php
Figure 51

52. In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response, as shown in Figure 52

JSON

[
    {
        "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
    }
]

In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response
Figure 52

53. Test the only option. In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages/1 , which returns a 405 response (Method Not Allowed. Because the ‘view’ action is not supported). The test passes, as shown in Figure 53

Test the only option. In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages/1 , which returns a 405 response (Method Not Allowed. Because the 'view' action is not supported). The test passes
Figure 53

54. Configure URL rules by modifying the configuration of the urlManager component in the application configuration to support the v1 module. Edit: \api\config\_urlManager.php, as shown in Figure 54
Note: Uncomment the only option configuration to subsequently support all actions

PHP
<?php return [ 'class' => yii\web\UrlManager::class,
    'enablePrettyUrl' => true,
    'enableStrictParsing' => true,
    'showScriptName' => false,
    'rules' => [
        [
            'class' => 'yii\rest\UrlRule',
            'controller' => ['v1/page'],
        ],
    ],
];
Configure URL rules by modifying the configuration of the urlManager component in the application configuration to support the v1 module. Edit: \api\config\_urlManager.php
Figure 54

55. In Postman, send a GET request to http://www.cmcp-api.localhost/v1 , which returns a 404 response in HTML format, as shown in Figure 55

HTML
<!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>
In Postman, send a GET request to http://www.cmcp-api.localhost/v1 , which returns a 404 response in HTML format
Figure 55

56. To resolve the 404 response being in HTML format, edit \api\config\web.php to set the default response format to JSON, as shown in Figure 56

PHP

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

To resolve the 404 response being in HTML format, edit \api\config\web.php to set the default response format to JSON
Figure 56

57. In Postman, send a GET request to http://www.cmcp-api.localhost/v1 , which returns a 404 response in JSON format, as shown in Figure 57

JSON

{
    "name": "Not Found",
    "message": "Page not found.",
    "code": 0,
    "status": 404,
    "type": "yii\\web\\NotFoundHttpException"
}

In Postman, send a GET request to http://www.cmcp-api.localhost/v1 , which returns a 404 response in JSON format
Figure 57

58. Implement data serialization by including pagination information in the response body to simplify client development. Edit \api\controllers\PageController.php, as shown in Figure 58

PHP
<?php namespace api\controllers; use yii\rest\ActiveController; class PageController extends ActiveController { public $serializer = [ 'class' => 'yii\rest\Serializer',
        'collectionEnvelope' => 'items',
    ];
}
Implement data serialization by including pagination information in the response body to simplify client development. Edit \api\controllers\PageController.php
Figure 58

59. In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response with pagination information included in the response body, as shown in Figure 59

JSON

{
    "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
    }
}

In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response with pagination information included in the response body
Figure 59

60. RESTful APIs are typically stateless; therefore, configure the user application component. Edit \api\config\web.php, as shown in Figure 60
Note: Configure the user application component:
Set the [[yii\web\User::enableSession|enableSession]] property to false.
Set the [[yii\web\User::loginUrl|loginUrl]] property to null to display an HTTP 403 error instead of redirecting to the login page.

PHP

        'user' => [
            'class' => yii\web\User::class,
            'identityClass' => common\models\User::class,
            'enableSession' => false,
            'loginUrl' => null,
            'enableAutoLogin' => false,
            'as afterLogin' => common\behaviors\LoginTimestampBehavior::class
        ],

RESTful APIs are typically stateless; therefore, configure the user application component. Edit \api\config\web.php
Figure 60

61. Copy Action.php, IndexAction.php, ViewAction.php, CreateAction.php, UpdateAction.php, DeleteAction.php, and Serializer.php from the \vendor\yiisoft\yii2\rest directory to the \api\rests\page directory, as shown in Figure 61

Copy Action.php, IndexAction.php, ViewAction.php, CreateAction.php, UpdateAction.php, DeleteAction.php, and Serializer.php from the \vendor\yiisoft\yii2\rest directory to the \api\rests\page directory
Figure 61

62. If the directory name consists of multiple words, it is recommended to use lowercase with underscores. Reference URL: https://github.com/hfcorriez/fig-standards/blob/master/accepted/zh_CN/PSR-0.md . Edit \api\controllers\PageController.php, as shown in Figure 62

PHP
<?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',
            ],
        ];
    }
}

Note: If only a few actions need to be supported, you can choose the following approach, for example:

PHP

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

        return $actions;
    }

If the directory name consists of multiple words, it is recommended to use lowercase with underscores. Reference URL: https://github.com/hfcorriez/fig-standards/blob/master/accepted/zh_CN/PSR-0.md . Edit \api\controllers\PageController.php
Figure 62

63. Edit \api\rests\page\IndexAction.php to adjust the namespace, inheritance relationship, query conditions, etc., as shown in Figure 63

PHP
<?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,
            ],
        ]);
    }
}


Edit \api\rests\page\IndexAction.php to adjust the namespace, inheritance relationship, query conditions, etc.
Figure 63

64. Edit \api\rests\page\Serializer.php to adjust the namespace, inheritance relationship, response structure (successful response: “code”: 10000, “message”, “data”; failed response: “code”: a number other than 10000, “message”), etc., as shown in Figure 64

PHP
<?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];
    }
}


Edit \api\rests\page\Serializer.php to adjust the namespace, inheritance relationship, response structure (successful response: "code": 10000, "message", "data"; failed response: "code": a number other than 10000, "message"), etc.
Figure 64

65. Edit \api\config\base.php to configure the i18n application component for the API application, as shown in Figure 65

PHP

        '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'=>[
                    ],
                ],
            ],
        ],

Edit \api\config\base.php to configure the i18n application component for the API application
Figure 65

66. Create language pack file: \api\messages\zh\error.php (Simplified Chinese, failed response), as shown in Figure 66

PHP

return [
    20000 => 'error',
    20001 => '页面列表为空',
];

Create language pack file: \api\messages\zh\error.php (Simplified Chinese, failed response)
Figure 66

67. Create language pack file: \api\messages\en\app.php (English (United States), successful response), as shown in Figure 67

PHP

return [
    10000 => 'success',
    10001 => 'Get page list is successful',
];

Create language pack file: \api\messages\en\app.php (English (United States), successful response)
Figure 67

68. Create language pack file: \api\messages\en\error.php (English (United States), failed response), as shown in Figure 68

PHP

return [
    20000 => 'error',
    20001 => 'Page list is empty',
];

Create language pack file: \api\messages\en\error.php (English (United States), failed response)
Figure 68

69. Open the URL: http://backend.cmcp-api.localhost/page/update?id=1 to edit the article and uncheck the active checkbox, as shown in Figure 69

Open the URL: http://backend.cmcp-api.localhost/page/update?id=1 to edit the article and uncheck the active checkbox
Figure 69

70. In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response. The list of active pages is empty, as shown in Figure 70
Note:
Accept application/json; version=0.0

JSON

{
    "code": 20001,
    "message": "Page list is empty"
}

In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response. The list of active pages is empty
Figure 70

71. In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response. The list of active pages is empty, as shown in Figure 71
Note:
Accept application/json; version=0.0
Accept-Language zh-CN

JSON

{
    "code": 20001,
    "message": "页面列表为空"
}

In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response. The list of active pages is empty
Figure 71

72. Open the URL: http://backend.cmcp-api.localhost/page/update?id=1 to edit the article and check the active checkbox, as shown in Figure 72

Open the URL: http://backend.cmcp-api.localhost/page/update?id=1 to edit the article and check the active checkbox
Figure 72

73. In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response. The list of active pages is not empty, as shown in Figure 73
Note:
Accept application/json; version=0.0
Accept-Language en-US

JSON

{
    "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
        }
    }
}

In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response. The list of active pages is not empty
Figure 73

74. In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response. The list of active pages is not empty, as shown in Figure 74
Note:
Accept application/json; version=0.0
Accept-Language zh-CN

JSON

{
    "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
        }
    }
}

In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response. The list of active pages is not empty
Figure 74

75. GET /pages/1: Returns the detailed information of page 1. Edit \api\rests\page\Action.php to adjust the namespace, inheritance relationship, response structure, etc., as shown in Figure 75

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


GET /pages/1: Returns the detailed information of page 1. Edit \api\rests\page\Action.php to adjust the namespace, inheritance relationship, response structure, etc.
Figure 75

76. Edit \api\rests\page\ViewAction.php to adjust the namespace, inheritance relationship, response structure, etc., as shown in Figure 76

PHP
<?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];
    }
}


Edit \api\rests\page\ViewAction.php to adjust the namespace, inheritance relationship, response structure, etc.
Figure 76

77. Edit the language pack files: \api\messages\zh\app.php (Simplified Chinese, successful response), \api\messages\zh\error.php (Simplified Chinese, failed response), \api\messages\en\app.php (English (United States), successful response), \api\messages\en\error.php (English (United States), failed response), as shown in Figure 77
\api\messages\zh\app.php

PHP

return [
    10000 => 'success',
    10001 => '获取页面列表成功',
    10002 => '获取页面详情成功',
];

\api\messages\zh\error.php

PHP

return [
    20000 => 'error',
    20001 => '页面列表为空',
    20002 => '页面ID:{id},不存在',
    20003 => '页面ID:{id},的状态不活跃',
];

\api\messages\en\app.php

PHP

return [
    10000 => 'success',
    10001 => 'Get page list is successful',
    10002 => 'Get page details succeeded',
];

\api\messages\en\error.php

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',
];

Edit the language pack files: \api\messages\zh\app.php (Simplified Chinese, successful response), \api\messages\zh\error.php (Simplified Chinese, failed response), \api\messages\en\app.php (English (United States), successful response), \api\messages\en\error.php (English (United States), failed response)
Figure 77

78. In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages/1 , which returns a 200 response. Its status is active, as shown in Figure 78
Note:
Accept application/json; version=0.0
Accept-Language zh-CN

JSON

{
    "code": 10000,
    "message": "获取页面详情成功",
    "data": {
        "id": 2,
        "slug": "contact",
        "title": "Contact",
        "body": "

Contact

",
        "view": "",
        "status": 1,
        "created_at": 1515488912,
        "updated_at": 1515488912
    }
}

In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages/1 , which returns a 200 response. Its status is active
Figure 78

79. In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages/1 , which returns a 200 response. Its status is active, as shown in Figure 79
Note:
Accept application/json; version=0.0
Accept-Language en-US

JSON

{
    "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
    }
}

In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages/1 , which returns a 200 response. Its status is active
Figure 79

80. In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages/2 , which returns a 200 response. Its status is inactive, as shown in Figure 80
Note:
Accept application/json; version=0.0
Accept-Language en-US

JSON

{
    "code": 20003,
    "message": "Page ID: 2, the status is not active"
}

In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages/2 , which returns a 200 response. Its status is inactive
Figure 80

81. In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages/3 , which returns a 404 response. The page does not exist, as shown in Figure 81
Note:
Accept application/json; version=0.0
Accept-Language zh-CN

JSON

{
    "name": "Not Found",
    "message": "页面ID:3,不存在",
    "code": 20002,
    "status": 404,
    "type": "yii\\web\\NotFoundHttpException"
}

In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages/3 , which returns a 404 response. The page does not exist
Figure 81

82. Implement the [[yii\web\Linkable]] interface to support HATEOAS, returning links related to the resource object. Edit the resource class \api\models\Page.php, as shown in Figure 82

PHP
<?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),
 
        ];
    }
}
Implement the [[yii\web\Linkable]] interface to support HATEOAS, returning links related to the resource object. Edit the resource class \api\models\Page.php
Figure 82

83. In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages/1 , which returns a 200 response and supports HATEOAS, as shown in Figure 83

Note:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br

JSON

{
    "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"
            }
        }
    }
}

In Postman, send a GET request to http://www.cmcp-api.localhost/v1/pages/1 , which returns a 200 response and supports HATEOAS
Figure 83

84. POST /pages: Create a new page. Edit \api\rests\page\CreateAction.php, as shown in Figure 84

PHP
<?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];
    }
}


POST /pages: Create a new page. Edit \api\rests\page\CreateAction.php
Figure 84

85. In Postman, send a POST request to http://www.cmcp-api.localhost/v1/pages , which returns a 201 response, as shown in Figure 85
Note:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded

JSON

{
    "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"
            }
        }
    }
}

In Postman, send a POST request to http://www.cmcp-api.localhost/v1/pages , which returns a 201 response
Figure 85

86. In Postman, send a POST request to http://www.cmcp-api.localhost/v1/pages with the parameters kept as they are, which returns a 422 response, as shown in Figure 86
Note:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded

JSON

{
    "code": 20004,
    "message": "数据验证失败:Slug的值\"slug-20180110-4\"已经被占用了。"
}

In Postman, send a POST request to http://www.cmcp-api.localhost/v1/pages with the parameters kept as they are, which returns a 422 response
Figure 86

87. In Postman, send a POST request to http://www.cmcp-api.localhost/v1/pages , which returns a 422 response (Data validation failed (for example, responding to a POST request). Please check the detailed error messages in the response body.), as shown in Figure 87
Note:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded

Body
Missing title parameter

JSON

{
    "code": 20004,
    "message": "数据验证失败:Title不能为空。"
}

In Postman, send a POST request to http://www.cmcp-api.localhost/v1/pages , which returns a 422 response (Data validation failed (for example, responding to a POST request). Please check the detailed error messages in the response body.)
Figure 87

88. In Postman, send a POST request to http://www.cmcp-api.localhost/v1/pages , which returns a 422 response, as shown in Figure 88
Note:
Accept application/json; version=0.0
Accept-Language en-US
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded

Body
Missing title parameter

JSON

{
    "code": 20004,
    "message": "Data validation failed: Title cannot be blank."
}

In Postman, send a POST request to http://www.cmcp-api.localhost/v1/pages , which returns a 422 response
Figure 88

89. PUT /pages/4: Update a page. Edit \api\rests\page\UpdateAction.php, as shown in Figure 89

PHP
<?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];
    }
}


PUT /pages/4: Update a page. Edit \api\rests\page\UpdateAction.php
Figure 89

90. In Postman, send a PUT request to http://www.cmcp-api.localhost/v1/pages/4 , which returns a 200 response, as shown in Figure 90

JSON

{
    "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"
            }
        }
    }
}

In Postman, send a PUT request to http://www.cmcp-api.localhost/v1/pages/4 , which returns a 200 response
Figure 90

91. In Postman, send a PUT request to http://www.cmcp-api.localhost/v1/pages/4 , which returns a 422 response, as shown in Figure 91
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 slug has already been taken by another page

JSON

{
    "code": 20004,
    "message": "Data validation failed: Slug \"slug-20180110-5\" has already been taken."
}

In Postman, send a PUT request to http://www.cmcp-api.localhost/v1/pages/4 , which returns a 422 response
Figure 91

92. DELETE /pages/4: Delete page 4. Edit \api\rests\page\DeleteAction.php, as shown in Figure 92

PHP
<?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')];
    }
}


DELETE /pages/4: Delete page 4. Edit \api\rests\page\DeleteAction.php
Figure 92

93. In Postman, send a DELETE request to http://www.cmcp-api.localhost/v1/pages/7 , which returns a 200 response, as shown in Figure 93
Note:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br

JSON

{
    "code": 10000,
    "message": "删除页面成功"
}

In Postman, send a DELETE request to http://www.cmcp-api.localhost/v1/pages/7 , which returns a 200 response
Figure 93

94. Show the final contents of the language pack files, as shown in Figure 94
\api\messages\zh\app.php

PHP

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

\api\messages\zh\error.php

PHP

return [
    20000 => 'error',
    20001 => '页面列表为空',
    20002 => '页面ID:{id},不存在',
    20003 => '页面ID:{id},的状态不活跃',
    20004 => '数据验证失败:{firstErrors}',
];

\api\messages\en\app.php

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

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}',
];

Show the final contents of the language pack files
Figure 94

95. OPTIONS /pages: Shows the verbs supported by the endpoint /pages. In Postman, send an OPTIONS request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response, as shown in Figure 95

OPTIONS /pages: Shows the verbs supported by the endpoint /pages. In Postman, send an OPTIONS request to http://www.cmcp-api.localhost/v1/pages , which returns a 200 response
Figure 95

96. OPTIONS /pages/1: Shows the verbs supported by the endpoint /pages/1. In Postman, send an OPTIONS request to http://www.cmcp-api.localhost/v1/pages/1 , which returns a 200 response, as shown in Figure 96

OPTIONS /pages/1: Shows the verbs supported by the endpoint /pages/1. In Postman, send an OPTIONS request to http://www.cmcp-api.localhost/v1/pages/1 , which returns a 200 response
Figure 96

97. Summary: The currently supported actions are: index, view, create, update, delete, options. Except for options, they are basically implemented by overriding after inheritance to meet specific requirements.

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系