本文实例分析了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程序设计有所帮助。