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

Create a GitHub Repository for yiisoft/yii2-app-advanced and Implement Hprose 2.0 RPC Server and Client

The reason for the above is that the Windows nginx PHP environment does not support concurrency. When accessing multiple domains simultaneously that all point to your local service, concurrency is not supported. In nginx.conf, modify the fastcgi_pass port number for different servers and start multiple php-cgi processes.

1. Implement the corresponding RPC service for Page in the rpc directory, create the Remote Procedure Call HTTP server, and create \rpc\controllers\ServerController.php

PHP
<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/08/01
 * Time: 18:02
 */
 
namespace rpc\controllers;
 
use Yii;
use yii\web\Controller;
use Hprose\Yii\Server;
 
/**
 * 远程过程调用 HTTP 服务器
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class ServerController extends Controller
{
    public $enableCsrfValidation = false;
 
    public function beforeAction($action)
    {
        parent::beforeAction($action);
        $server = new Server();
        $server->addMethod($action->actionMethod, $this, $action->controller->id . '_' . $action->id);
        $server->start();
    }
}

2. Create the Page model \rpc\models\Page.php

PHP
<?php
 
namespace rpc\models;
 
 
class Page extends \common\logics\Page
{
 
}

3. Create the Page controller \rpc\controllers\PageController.php

PHP
<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/08/01
 * Time: 15:01
 */
 
namespace rpc\controllers;
 
use Yii;
use rpc\models\Page;
 
/**
 * Page Controller
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class PageController extends ServerController
{
    /**
     * 创建页面
     *
     * @param array $data 数据
     *
     * @param string $version 版本号(次版本号与修订号)
     * 格式如下:
     * 2.3
     *
     * @param string $language 区域和语言
     * 格式如下:
     * en-US
     *
     * @return mixed
     */
    public function actionCreate(array $data, string $version = '', string $language = '')
    {
        return $data;
        //echo 1;
        //exit;
        //$fileName = date('Y-m-d-H-i-s', time()) . '-' . microtime(true);
        //file_put_contents('./../runtime/' . $fileName . '.txt', '0');
        //return 1;
    }
}
 

4. Then open the URL http://rpc.github-shuijingwan-yii2-app-advanced.localhost/page/create in the browser. You will see the following output: Fa2{u#s11″page_create”}z, which is as expected

5. Create the client via the factory method create. Edit \api\rests\page\CreateAction.php

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;
use Hprose\Http\Client;
 
/**
 * 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);
        }
 
        $client = Client::create('http://rpc.github-shuijingwan-yii2-app-advanced.localhost/page/create', false);
        $page = $client->page_create([
            'title' => 'title',
            'body' => 'body',
        ]);
 
        print_r($page);
        exit;
 
        /* @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;
                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', '10805'), 'data' => $model];
    }
}
 
 

6. In Postman, POST to http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/pages. The response fails

JSON
{
    "name": "Exception",
    "message": "28: Operation timed out after 30000 milliseconds with 0 bytes received",
    "code": 0,
    "type": "Exception",
    "file": "E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\hprose\\hprose\\src\\Hprose\\Http\\Client.php",
    "line": 276,
    "stack-trace": [
        "#0 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\hprose\\hprose\\src\\Hprose\\Http\\Client.php(295): Hprose\\Http\\Client->syncSendAndReceive('Cs11\"page_creat...', Object(stdClass))",
        "#1 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\hprose\\hprose\\src\\Hprose\\Client.php(539): Hprose\\Http\\Client->sendAndReceive('Cs11\"page_creat...', Object(stdClass))",
        "#2 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\hprose\\hprose\\src\\Hprose\\Client.php(109): Hprose\\Client->afterFilterHandler('Cs11\"page_creat...', Object(stdClass))",
        "#3 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\hprose\\hprose\\src\\Hprose\\Client.php(504): Hprose\\Client->Hprose\\{closure}('Cs11\"page_creat...', Object(stdClass))",
        "#4 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\hprose\\hprose\\src\\Hprose\\Client.php(518): Hprose\\Client->syncBeforeFilterHandler('Cs11\"page_creat...', Object(stdClass))",
        "#5 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\hprose\\hprose\\src\\Hprose\\Client.php(106): Hprose\\Client->beforeFilterHandler('Cs11\"page_creat...', Object(stdClass))",
        "#6 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\hprose\\hprose\\src\\Hprose\\Client.php(476): Hprose\\Client->Hprose\\{closure}('Cs11\"page_creat...', Object(stdClass))",
        "#7 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\hprose\\hprose\\src\\Hprose\\Client.php(489): Hprose\\Client->syncInvokeHandler('page_create', Array, Object(stdClass))",
        "#8 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\hprose\\hprose\\src\\Hprose\\Client.php(103): Hprose\\Client->invokeHandler('page_create', Array, Object(stdClass))",
        "#9 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\hprose\\hprose\\src\\Hprose\\Client.php(608): Hprose\\Client->Hprose\\{closure}('page_create', Array, Object(stdClass))",
        "#10 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\hprose\\hprose\\src\\Hprose\\Client.php(438): Hprose\\Client->invoke('page_create', Array)",
        "#11 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\api\\rests\\page\\CreateAction.php(49): Hprose\\Client->__call('page_create', Array)",
        "#12 [internal function]: api\\rests\\page\\CreateAction->run()",
        "#13 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\base\\Action.php(94): call_user_func_array(Array, Array)",
        "#14 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\base\\Controller.php(157): yii\\base\\Action->runWithParams(Array)",
        "#15 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\base\\Module.php(528): yii\\base\\Controller->runAction('create', Array)",
        "#16 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\web\\Application.php(103): yii\\base\\Module->runAction('v1/page/create', Array)",
        "#17 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\vendor\\yiisoft\\yii2\\base\\Application.php(386): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))",
        "#18 E:\\wwwroot\\github-shuijingwan-yii2-app-advanced\\api\\web\\index.php(17): yii\\base\\Application->run()",
        "#19 {main}"
    ]
}

7. The reason for the above is that the Windows nginx PHP environment does not support concurrency. When accessing multiple domains simultaneously that all point to your local service, concurrency is not supported. In nginx.conf, modify the fastcgi_pass port number for different servers and start multiple php-cgi processes. See Figure 1

The reason for the above is that the Windows nginx PHP environment does not support concurrency. When accessing multiple domains simultaneously that all point to your local service, concurrency is not supported. In nginx.conf, modify the fastcgi_pass port number for different servers and start multiple php-cgi processes.
Figure 1
Nginx
## FRONTEND ##
server {
    charset utf-8;
    client_max_body_size 128M;

    listen 80; ## listen for ipv4
    #listen [::]:80 default_server ipv6only=on; ## listen for ipv6

    server_name www.github-shuijingwan-yii2-app-advanced.localhost;
    root        E:/wwwroot/github-shuijingwan-yii2-app-advanced/frontend/web;
    index       index.php;

    access_log  logs/www.github-shuijingwan-yii2-app-advanced.localhost.access.log;
    error_log   logs/www.github-shuijingwan-yii2-app-advanced.localhost.error.log;

	location / {
		# Redirect everything that isn't a real file to index.php
		try_files $uri $uri/ /index.php$is_args$args;
	}

	# uncomment to avoid processing of calls to non-existing static files by Yii
	#location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
	#    try_files $uri =404;
	#}
	#error_page 404 /404.html;

	# deny accessing php files for the /assets directory
	location ~ ^/assets/.*\.php$ {
		deny all;
	}

	location ~ \.php$ {
		include fastcgi_params;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		fastcgi_pass 127.0.0.1:9000;
		#fastcgi_pass unix:/var/run/php5-fpm.sock;
		try_files $uri =404;
	}

	location ~* /\. {
		deny all;
	}
}

## BACKEND ##
server {
    charset utf-8;
    client_max_body_size 128M;

    listen 80; ## listen for ipv4
    #listen [::]:80 default_server ipv6only=on; ## listen for ipv6

    server_name backend.github-shuijingwan-yii2-app-advanced.localhost;
    root        E:/wwwroot/github-shuijingwan-yii2-app-advanced/backend/web;
    index       index.php;

    access_log  logs/backend.github-shuijingwan-yii2-app-advanced.localhost.access.log;
    error_log   logs/backend.github-shuijingwan-yii2-app-advanced.localhost.error.log;

	location / {
		# Redirect everything that isn't a real file to index.php
		try_files $uri $uri/ /index.php$is_args$args;
	}

	# uncomment to avoid processing of calls to non-existing static files by Yii
	#location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
	#    try_files $uri =404;
	#}
	#error_page 404 /404.html;

	# deny accessing php files for the /assets directory
	location ~ ^/assets/.*\.php$ {
		deny all;
	}

	location ~ \.php$ {
		include fastcgi_params;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		fastcgi_pass 127.0.0.1:9000;
		#fastcgi_pass unix:/var/run/php5-fpm.sock;
		try_files $uri =404;
	}

	location ~* /\. {
		deny all;
	}
}

## API ##
server {
    charset utf-8;
    client_max_body_size 128M;

    listen 80; ## listen for ipv4
    #listen [::]:80 default_server ipv6only=on; ## listen for ipv6

    server_name api.github-shuijingwan-yii2-app-advanced.localhost;
    root        E:/wwwroot/github-shuijingwan-yii2-app-advanced/api/web;
    index       index.php;

    access_log  logs/api.github-shuijingwan-yii2-app-advanced.localhost.access.log;
    error_log   logs/api.github-shuijingwan-yii2-app-advanced.localhost.error.log;

	location / {
		# Redirect everything that isn't a real file to index.php
		try_files $uri $uri/ /index.php$is_args$args;
	}

	# uncomment to avoid processing of calls to non-existing static files by Yii
	#location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
	#    try_files $uri =404;
	#}
	#error_page 404 /404.html;

	# deny accessing php files for the /assets directory
	location ~ ^/assets/.*\.php$ {
		deny all;
	}

	location ~ \.php$ {
		include fastcgi_params;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		fastcgi_pass 127.0.0.1:9001;
		#fastcgi_pass unix:/var/run/php5-fpm.sock;
		try_files $uri =404;
	}

	location ~* /\. {
		deny all;
	}
}

## RPC ##
server {
    charset utf-8;
    client_max_body_size 128M;

    listen 80; ## listen for ipv4
    #listen [::]:80 default_server ipv6only=on; ## listen for ipv6

    server_name rpc.github-shuijingwan-yii2-app-advanced.localhost;
    root        E:/wwwroot/github-shuijingwan-yii2-app-advanced/rpc/web;
    index       index.php;

    access_log  logs/rpc.github-shuijingwan-yii2-app-advanced.localhost.access.log;
    error_log   logs/rpc.github-shuijingwan-yii2-app-advanced.localhost.error.log;

	location / {
		# Redirect everything that isn't a real file to index.php
		try_files $uri $uri/ /index.php$is_args$args;
	}

	# uncomment to avoid processing of calls to non-existing static files by Yii
	#location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
	#    try_files $uri =404;
	#}
	#error_page 404 /404.html;

	# deny accessing php files for the /assets directory
	location ~ ^/assets/.*\.php$ {
		deny all;
	}

	location ~ \.php$ {
		include fastcgi_params;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		fastcgi_pass 127.0.0.1:9000;
		#fastcgi_pass unix:/var/run/php5-fpm.sock;
		try_files $uri =404;
	}

	location ~* /\. {
		deny all;
	}
}

## MISC ##

### WWW Redirect ###
server {
    listen       80;
    server_name  github-shuijingwan-yii2-app-advanced.localhost;
    return       301 http://www.github-shuijingwan-yii2-app-advanced.localhost$request_uri;
}


8. In Postman, POST to http://api.github-shuijingwan-yii2-app-a-client.localhost/v1/pages. The response succeeds

Plaintext
Array
(
    [title] => title
    [body] => body
)


9. Create the v1 module to implement version control based on modules. Open http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/module to generate the v1 module. See Figure 2

Create the v1 module to implement version control based on modules. Open http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/module to generate the v1 module
Figure 2

10. Rename \rpc\modules\v1\controllers\DefaultController.php to \rpc\modules\v1\controllers\PageController.php

PHP
<?php
 
namespace rpc\modules\v1\controllers;
 
/**
 * Page controller for the `v1` module
 */
class PageController extends \rpc\controllers\PageController
{
    public $modelClass = 'rpc\modules\v1\models\Page';
}

11. Delete \rpc\modules\v1\views

12. Copy \api\models\Page.php, \api\models\PageQuery.php, and \api\models\PageSearch.php to \rpc\models\Page.php, \rpc\models\PageQuery.php, and \rpc\models\PageSearch.php, and adjust the namespaces

13. Copy \api\modules\v1\models\Page.php, \api\modules\v1\models\PageQuery.php, and \api\modules\v1\models\PageSearch.php to \rpc\modules\v1\models\Page.php, \rpc\modules\v1\models\PageQuery.php, and \rpc\modules\v1\models\PageSearch.php, and adjust the namespaces

14. Edit \rpc\config\main.php to add the module to the modules property list in the application configuration

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

15. Open the URL http://rpc.github-shuijingwan-yii2-app-advanced.localhost/v1/page/create in the browser. You will see the following output: Fa2{u#s11″page_create”}z, which is as expected

16. Create the client via the factory method create. Edit \api\rests\page\CreateAction.php and adjust the HTTP service address

PHP
        $client = Client::create('http://rpc.github-shuijingwan-yii2-app-advanced.localhost/v1/page/create', false);
        $page = $client->page_create([
            'title' => 'title',
            'body' => 'body',
        ]);

        print_r($page);
        exit;

17. In Postman, POST to http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/pages. The response is normal

Plaintext
Array
(
    [title] => title
    [body] => body
)


18. Edit \environments\dev\common\config\params-local.php and \environments\prod\common\config\params-local.php

PHP
<?php
return [
    // RPC HTTP 服务地址
    'rpc' => [
        'hostInfo' => 'http://rpc.github-shuijingwan-yii2-app-advanced.localhost', // HOME URL
        'baseUrl' => '/v1', // BASE URL
    ],
];

19. Create the base class for the RPC client: \common\logics\rpc\Model.php

PHP
<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/08/02
 * Time: 13:12
 */
 
namespace common\logics\rpc;
 
use Yii;
use Hprose\Http\Client;
 
/**
 * RPC 客户端的基础类
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class Model extends \yii\base\Model
{
    /**
     * 创建一个同步的 HTTP 客户端
     *
     * @param string $controllerId 控制器ID
     * 格式如下:
     * page
     *
     * @param string $actionId 方法ID
     * 格式如下:
     * create
     *
     * @return object $client 同步的 HTTP 客户端
     */
    public static function client($controllerId, $actionId)
    {
        $url = Yii::$app->params['rpc']['hostInfo'] . Yii::$app->params['rpc']['baseUrl'];
        $client = Client::create($url . '/' . $controllerId . '/' . $actionId, false);
        return $client->$controllerId;
    }
}

20. Create the common logic class for the Page model: \common\logics\rpc\Page.php. Define model properties, scenarios (massive assignment), and validation rules (perform preliminary validation of request data to prevent obviously invalid data from penetrating to the RPC server)

PHP
<?php
 
namespace common\logics\rpc;
 
use Yii;
 
/**
 * 页面模型
 */
class Page extends Model
{
    const STATUS_DELETED = -1; //状态:删除
    const STATUS_DISABLED = 0; //状态:禁用
    const STATUS_DRAFT = 1; //状态:草稿
    const STATUS_PUBLISHED = 2; //状态:发布
 
    const CONTROLLER_ID = 'page'; //控制器ID
 
    const SCENARIO_CREATE = 'create';
 
    public $slug;
    public $title;
    public $body;
    public $view;
    public $status;
 
    /**
     * {@inheritdoc}
     */
    public function scenarios()
    {
        $scenarios = parent::scenarios();
        $scenarios[self::SCENARIO_CREATE] = ['slug', 'title', 'body', 'view', 'status'];
 
        return $scenarios;
    }
 
    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['title', 'body'], 'required'],
            [['body'], 'string'],
            [['view', 'status'], 'integer'],
            [['slug', 'title'], 'string', 'max' => 255],
        ];
    }
 
    /**
     * {@inheritdoc}
     */
    public function attributeLabels()
    {
        return [
            'slug' => Yii::t('model/rpc/page', 'Slug'),
            'title' => Yii::t('model/rpc/page', 'Title'),
            'body' => Yii::t('model/rpc/page', 'Body'),
            'view' => Yii::t('model/rpc/page', 'View'),
            'status' => Yii::t('model/rpc/page', 'Status'),
        ];
    }
 
    /**
     * 创建页面
     *
     * @param array $data 数据
     *
     * @param string $version 版本号(次版本号与修订号)
     * 格式如下:
     * 2.3
     *
     * @param string $language 区域和语言
     * 格式如下:
     * en-US
     *
     * @return array
     */
    public function create(array $data, string $version, string $language)
    {
        $actionId = 'create';
        return self::client(self::CONTROLLER_ID, $actionId)->$actionId($data, $version, $language);
    }
 
}
 

21. Create \api\models\rpc\Page.php

PHP
<?php
 
namespace api\models\rpc;
 
class Page extends \common\logics\rpc\Page
{
 
}

22. Create \api\modules\v1\models\rpc\Page.php

PHP
<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/08/02
 * Time: 13:04
 */
 
namespace api\modules\v1\models\rpc;
 
 
class Page extends \api\models\rpc\Page
{
 
}

23. Edit \api\modules\v1\controllers\PageController.php to specify the model class

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\rpc\Page';
}

23. Edit \common\logics\Page.php to define scenarios

PHP
<?php
 
namespace common\logics;
 
use Yii;
use yii\behaviors\SluggableBehavior;
use yii\behaviors\TimestampBehavior;
use yii2tech\ar\softdelete\SoftDeleteBehavior;
use yii\helpers\ArrayHelper;
 
class Page extends \common\models\Page
{
    const STATUS_DELETED = -1; //状态:删除
    const STATUS_DISABLED = 0; //状态:禁用
    const STATUS_DRAFT = 1; //状态:草稿
    const STATUS_PUBLISHED = 2; //状态:发布
 
    const SCENARIO_CREATE = 'create';
 
    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            'timestampBehavior' => [
                'class' => TimestampBehavior::className(),
                'attributes' => [
                    self::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
                    self::EVENT_BEFORE_UPDATE => 'updated_at',
                    SoftDeleteBehavior::EVENT_BEFORE_SOFT_DELETE => 'updated_at',
                ]
            ],
            'slug' => [
                'class' => SluggableBehavior::className(),
                'attribute' => 'title',
                'ensureUnique' => true,
                'immutable' => true
            ],
            'softDeleteBehavior' => [
                'class' => SoftDeleteBehavior::className(),
                'softDeleteAttributeValues' => [
                    'status' => self::STATUS_DELETED
                ],
            ],
        ];
    }
 
    /**
     * {@inheritdoc}
     */
    public function scenarios()
    {
        $scenarios = parent::scenarios();
        $scenarios[self::SCENARIO_CREATE] = ['slug', 'title', 'body', 'view', 'status'];
 
        return $scenarios;
    }
 
    /**
     * @inheritdoc
     */
    public function rules()
    {
        $rules = [
            [['title', 'body'], 'required'],
            [['slug'], 'unique'],
            ['view', 'default', 'value' => 0],
            ['status', 'default', 'value' => self::STATUS_DRAFT],
        ];
        $parentRules = parent::rules();
 
        unset($parentRules[0]);
 
        return ArrayHelper::merge($rules, $parentRules);
    }
 
    /**
     * {@inheritdoc}
     * @return PageQuery the active query used by this AR class.
     */
    public static function find()
    {
        return new PageQuery(get_called_class());
    }
 
}

24. Edit \rpc\config\main.php to support internationalization

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

25. Edit \api\rests\page\CreateAction.php

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';
 
    public $createScenario = 'create';
 
 
    /**
     * 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->createScenario,
        ]);
 
        $model->load(Yii::$app->getRequest()->getBodyParams(), '');
 
        // 模型转换成数组
        $data = $model->attributes;
 
        // 内容协商
        $response = Yii::$app->response;
        $acceptParams = $response->acceptParams;
        if (isset($acceptParams['version'])) {
            $version = $acceptParams['version'];
        } else {
            $version = '';
        }
 
        if ($model->validate()) {
            $data = $model->create($data, $version, Yii::$app->language);
            if ($data['code'] === 10000) {
                $response = Yii::$app->getResponse();
                $response->setStatusCode(201);
                $id = $data['data']['id'];
                $response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true));
            }
            return $data;
        } 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.');
        }
    }
}
 
 

26. Edit \rpc\controllers\PageController.php

PHP
<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/08/01
 * Time: 15:01
 */
 
namespace rpc\controllers;
 
use Yii;
use yii\web\ServerErrorHttpException;
 
/**
 * Page Controller
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class PageController extends ServerController
{
    public $createScenario = 'create';
 
    /**
     * 创建页面
     *
     * @param array $data 数据
     *
     * @param string $version 版本号(次版本号与修订号)
     * 格式如下:
     * 2.3
     *
     * @param string $language 区域和语言
     * 格式如下:
     * en-US
     *
     * @return mixed
     */
    public function actionCreate(array $data, string $version = '', string $language = '')
    {
        if (!empty($language)) {
            Yii::$app->language = $language;
        }
 
        /* @var $model \yii\db\ActiveRecord */
        $model = new $this->modelClass([
            'scenario' => $this->createScenario,
        ]);
 
        if ($model->load($data, '') && $model->save()) {
            $data = $model->attributes;
        } elseif ($model->hasErrors()) {
            foreach ($model->getFirstErrors() as $message) {
                $firstErrors = $message;
                break;
            }
            return ['code' => 25004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '25004'), ['firstErrors' => $firstErrors]))];
        } elseif (!$model->hasErrors()) {
            throw new ServerErrorHttpException('Failed to create the object for unknown reason1.');
        }
 
        return ['code' => 10000, 'message' => Yii::t('success', '15805'), 'data' => $data];
    }
}
 

27. In Postman, POST to http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/pages. The response fails

JSON
{
	"title":"title-20180731-5"
}

Note: Interface model validation failed

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

28. In Postman, POST to http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/pages. The response fails

JSON
{
	"title":"title-20180731-5",
	"body":"title-20180731-5",
	"slug":"title-20180731-5"
}

Note: RPC model validation failed

JSON
{
    "code": 25004,
    "message": "数据验证失败:别名的值\"title-20180731-5\"已经被占用了。"
}

29. In Postman, POST to http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/pages. The response succeeds

JSON
{
	"title":"title-20180731-5",
	"body":"title-20180731-5"
}

JSON
{
    "code": 10000,
    "message": "创建页面成功",
    "data": {
        "id": 12,
        "slug": "title-20180731-5-7",
        "title": "title-20180731-5",
        "body": "title-20180731-5",
        "view": 0,
        "status": 1,
        "created_at": 1533205467,
        "updated_at": 1533205467
    }
}

系列导航

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

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

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

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

关于我  ·  GitHub  ·  邮件联系