本文实例分析了Yii2中Restful API原理。分享给大家供大家参考,具体如下:
Yii2 有个很重要的特性是对 Restful API的默认支持, 通过短短的几个配置就可以实现简单的对现有Model的RESTful API
这里通过分析rest部分源码,简单剖析下yii2 实现 restful 的原理,并通过一些定制实现 对 关联模型的RESTful api 操作。
~ 代表 extends from 的关系
| | rest/
| | |-Action.php ~ `\yii\base\Action`
| | |-Controller.php ~ `\yii\web\Controller`
| | | |-ActiveController.php ~ `rest\Controller`
| | |-Serializer.php ~ `yii\base\Component`
| | |-UrlRule.php ~ `yii\web\CompositeUrlRule`
| | |-CreateAction.php ~ `rest\Action`
| | |-DeleteAction.php ~ `rest\Action`
| | |-IndexAction.php ~ `rest\Action`
| | |-OptionsAction.php ~ `rest\Action`
| | |-UpdateAction.php ~ `rest\Action`
| | |-ViewAction.php ~ `rest\Action`
1. rest/Controller ~ \yii\web\Controller
Controller是 RESTful API 控制器类的基类
它在一个API请求的控制周期中一次实现了下面的步骤 1~5:
① 解析响应的内容格式
② 校验请求方法
③ 检验用户权限
④ 限制速度
⑤ 格式化响应数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
use yii\filters\auth\CompositeAuth;
use yii\filters\ContentNegotiator;
use yii\filters\RateLimiter;
use yii\web\Response;
use yii\filters\VerbFilter;
/**
* Controller is the base class for RESTful API controller classes.
*
* Controller implements the following steps in a RESTful API request handling cycle
* 1. Resolving response format (see [[ContentNegotiator]]);
* 2. Validating request method (see [[verbs()]]).
* 3. Authenticating user (see [[\yii\filters\auth\AuthInterface]]);
* 4. Rate limiting (see [[RateLimiter]]);
* 5. Formatting response data (see [[serializeData()]])
behaviors
contentNegotiator
verbFilter
authenticator
rateLimiter
afterAction
serializeData Yii::createObject($this->serializer)->serialize($data)
verbs []
*/
class Controller extends \yii\web\Controller
{
public $serializer = 'yii\rest\Serializer' ;
public $enableCsrfValidation = false;
public function behaviors()
{
return [
'contentNegotiator' => [
'class' => ContentNegotiator::className(),
'formats' => [
'application/json' => Response::FORMAT_JSON,
'application/xml' => Response::FORMAT_XML,
],
],
'verbFilter' => [
'class' => VerbFilter::className(),
'actions' => $this ->verbs(),
],
'authenticator' => [
'class' => CompositeAuth::className(),
],
'rateLimiter' => [
'class' => RateLimiter::className(),
],
]
}
public function verbs()
{
return [];
}
public function serializeData( $data )
{
return Yii::createObject( $this ->serializer)->serialize( $data );
}
public function afterAction( $action , $result )
{
$result = parent::afterAction( $action , $result );
return $this ->serializeData( $result );
}
}
|
2. rest/ActiveController ~ rest/Controller
ActiveController 实现了一系列的和 ActiveRecord 互通数据的RESTful方法
ActiveRecord 的类名由 modelClass 变量指明, yii\db\ActiveRecordInterface ???
默认的, 支持下面的方法:
* - `index`: list of models
* - `view`: return the details of a model
* - `create`: create a new model
* - `update`: update an existing model
* - `delete`: delete an existing model
* - `options`: return the allowed HTTP methods
可以通过覆盖 actions() 并且 unsetting 响应的 action 来禁用这些默认的动作。
要增加一个新的动作, 覆盖 actions() 向其末尾增加一个新的 action class 或者 是一个新的 action method
注意一点,确保你同时也覆盖了 verbs() 方法来声明这个新的动作支持那些HTTP Method
也需要覆盖checkAccess() 来检查当前用户是否有权限来执行响应的某个动作。
根据上面的说明再写一遍 Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
class ActiveController extends Controller
{
public #modelClass;
public $updateScenario = Model::SCENARIO_DEFAULT;
public $createScenario = Model::SCENARIO_DEFAULT;
public function init()
{
parent::init();
if ( $this ->modelClass == null){
throw new InvalidConfigException( 'The "modelClass" property must be set.' );
}
}
public function actions()
{
return [
'index' => [
'class' => 'app\controllers\rest\IndexAction' ,
'modelClass' => $this ->modelClass,
'checkAccess' => [ $this , 'checkAccess' ],
],
'view' ...
'create' ...
'update' ...
'delete' ...
'options' ...
];
}
protected function verbs()
{
return [
'index' => [ 'GET' , 'HEAD' ],
'view' =>[ 'GET' , 'HEAD' ],
'create' =>[ 'POST' ],
'update' =>[ 'PUT' , 'PATCH' ],
'delete' =>[ 'DELETE' ],
];
}
public function checkAccess( $action , $model =null, $params = [])
{
}
}
|
下面来实现一个继承自 这个rest\ActiveController的 News 控制器:
1
2
3
4
5
6
|
namespace app\controllers;
use app\controllers\rest\ActiveController; #刚才这个AC,我从yii/rest下面拷贝了一份出来
class NewsController extends ActiveController
{
public $modelClass = 'app\models\News' ;
}
|
定义到这里就足够实现 rest\ActiveController 里面的默认方法了
下面来覆盖下,实现一些定制的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
class NewsController extends ActiveController
{
public $modelClass = 'app\models\News' ;
#定制serializer
# public $serializer = 'yii\rest\Serializer' ;
public $serializer = [
'class' => 'app\controllers\rest\Serializer' ,
'collectionEnvelope' => 'items' ,
];
public function behaviors()
{
$be = ArrayHelper::merge(
parent::behaviors(),
[
'verbFilter' => [
'class' => VerbFilter::className(),
'actions' => [
'index' => [ 'get' ],
...
]
],
'authenticator' => [
'class' => CompositeAuth::className(),
'authMethods' => [
HttpBasicAuth::className(),
HttpBearerAuth::className(),
QueryParamAuth::className(),
]
],
'contentNegotiator' => [
'class' => ContentNegotiator::className(),
'formats' => [
'text/html' => Response::FORMAT_HTML,
]
],
'access' => [
'class' => AccessControl::className(),
'only' => [ 'view' ],
'rules' => [
[
'actions' => [ 'view' ],
'allow' => false,
'roles' => [ '@' ],
],
],
]
],
);
return $be ;
}
public function checkAccess()
{
}
}
|
3. 定制Actions
如果要对 Actions 进行大的改动,建议拷贝一份出来,不要使用原始的 yii\rest\XXXAction命名空间
我这里以要实现对related models进行 CURD 操作为目标进行大的改动
Action
在定制各个action之前, 先看看它们的基类 rest\Action, 主要是一个 findModel的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class Action extend \yii\base\Action
{
public $modelClass ;
public $findModel ;
public $checkAccess ;
public function init()
{
if ( $this ->modelClass == null) {
throw new InvalidConfigException(get_class( $this ). '::$modelClass must be set' );
}
}
public function findModel( $id )
{
if ( $this ->findModel !== null) {
return call_user_func( $this ->findModel, $id , $this );
}
$modelClass = $this ->modelClass;
$keys = $modelClass ::primaryKey();
if ( count ( $keys ) > 1) {
$values = explode ( ',' , $id );
if ..
} elseif ( $id !== null) {
$model = $modelClass ::findOne( $id );
}
if (isset( $model )){
return $model ;
} else {
throw new NotFoundHttpException( "Object not found: $id" );
}
}
}
|
view
view 动作不需要改动,因为 model 有 getRelated 的自有机制
1
2
3
4
5
6
7
8
9
10
|
class ViewAction extend Action
{
public function run( $id )
{
$model = $this ->findModel( $id );
if ( $this ->checkAccess) {
call_user_func( $this ->checkAccess, $this ->id, $model );
}
}
}
|
update
1
2
3
4
5
6
7
8
9
10
11
12
|
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(), '' );
$model ->save();
return $model ;
}
|
经过改造后,需要满足对关联模型的update动作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
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;
/*
*
* x-www-form-urlencoded key=>value
* image mmmmmmmm
* link nnnnnnnnnn
* newsItem[title]=>ttttttttttt , don't use newsItem["title"]
* newsItem[body]=>bbbbbbbbbbb
* don't use newsItem=>array("title":"tttttt","body":"bbbbbbb")
* don't use newsItem=>{"title":"ttttttt","body":"bbbbbbbb"}
*
*/
$newsItem = Yii:: $app ->getRequest()->getBodyParams()[ 'newsItem' ];
/*
Array
(
[title] => ttttttttttt
[body] => bbbbbbbbbbb
)
*/
$model ->newsItem->load( $newsItem , '' );
# $model ->newsItem->load(Yii:: $app ->getRequest()->getBodyParams(), '' );
#print_R( $model ->newsItem); exit ;
#print_R( $model ->newsItem); exit ;
if ( $model ->save())
{
$model ->load(Yii:: $app ->getRequest()->getBodyParams(), '' );
$model ->newsItem->save();
}
return $model ;
}
|
这里还应该对 newsItem save 失败 的情况进行处理,暂且不处理。
希望本文所述对大家基于Yii框架的PHP程序设计有所帮助。