Drupal7 中Restful模组应用简单介绍

Posted by Y Cheung on Wed, Jun 8, 2016

安装并启用Restful Module

  • Restful 2.x 下载
  • drush 安装 drush dl restfuldrush en restful

自定义RESTFUL API模组

  • 可参考restful里的范例 sites/all/modules/restful/modules/restful_example 或者测试文件内容 sites/all/modules/restful/tests

模组文件结构

1custom_restfulapi/
2├─ src/
3│  ├─ Plugin/
4│  │  ├─ resource/
5│  │  │  ├─ entity/
6│  │  │  │  ├─ Testentity__1_0.php
7├─ custom_restfulapi.info
8├─ custom_restfulapi.module

API RESOURCES要放在src/Plugin下。 *.info 和 *.module 的内容跟其他自定义模组的相差无几,方法一样,此处从略。

1name = RESTful custom
2description = Custom RESTful resource.
3core = 7.x
4dependencies[] = restful
5
6registry_autoload[] = PSR-4

建立API端点(API endpoint)

此处以建立暴露entity的API端点为例,一般地,在Plugin/resource里创建entity文件夹(非必须),然后新建一个PHP文件Testentity__1_0.php,文件内容大致如下:

 1<?php
 2
 3namespace Drupal\custom_restfulapi\Plugin\resource\entity;
 4
 5use Drupal\restful\Plugin\resource\ResourceEntity;
 6use Drupal\restful\Plugin\resource\ResourceInterface;
 7
 8/**
 9 * Class Testentity__1_0
10 * @package Drupal\custom_restfulapi\Plugin\resource\entity
11 *
12 * @Resource(
13 *   name = "Testentity:1.0",
14 *   description = "A simple testapi.",
15 *   resource = "testentity",
16 *   label = "Test entity ",
17 *   authenticationTypes = {
18 *   "token"
19 *   },
20 *   authenticationOptional = TRUE,
21 *   dataProvider = {
22 *     "entityType": "sampleentitytype",
23 *     "bundles": {
24 *       "samplebundle","samplebundle2"
25 *     },
26 *   },
27 *   majorVersion = 1,
28 *   minorVersion = 0
29 * )
30 */
31
32class Testentity__1_0 extends ResourceEntity implements ResourceInterface
33{
34    /**
35     * Overrides ResourceEntity::publicFields().
36     * @return array
37     */
38    protected function publicFields()
39    {
40        $public_fields = parent::publicFields();
41        $public_fields['customfieldname'] = array(
42            'property'=>'field_number',
43            'process_callbacks' => array(
44                array($this,'toFloat'),
45            ),
46        );
47        return $public_fields;
48    }
49
50    /**
51     * @param $value
52     * @return float
53     */
54    public function toFloat($value){
55        $value = (float)$value;
56        return $value;
57    }
58
59}

Restful 使用了注释(Annotation)定义资源。

  • resource=定义了API接口的URL,比如此处定义的API接口即为 example.com\api\testentity
  • authenticationTypes定义验证方式,默认的是HTTP BASIC AUTH,当启用了模组 RESTful token authentication时就可以用本例中的值启用Token authentication。获取token的方法参考这里,简而言之
    • GET /api/login-token
    • header参数(Authorization:Basic b2M6b2MxMjM0),b2M6b2MxMjM0为“用户名:密码”BASE64编码后的字符串;
    • 响应:
1{
2  "access_token": "D8uD2xjDtlT8ym0BiM-n4R-cssRACxgf34xsBQpgGKw",
3  "type": "Bearer",
4  "expires_in": 86400,
5  "refresh_token": "jhHpCr3FubMsSyE-9B6wPgtPMeQn2u34-P4blDSiXrw"
6}
  • authenticationOptional若值为TRUE则不验证,直接可以获取资源,这个设置在开发和测试的时候很好用;
  • dataProvider定义了数据来源,具体到了bundle,可以多个bundle;
  • majorVersionminorVersion 定义了版本号。

在类 Class Testentity__1_0 中,我们使用 publicFields() 定义输出json中的entity各字段的key和value值,默认输出id,labelself字段,如果不需要暴露可以在return $public_fieldsunset它们,比如这样:

1 unset($public_fields['self'],$public_fields['label']);
2 $public_fields['id']['methods']=array();

可以对每个字段值格式化,过滤等进行处理,添加一个回调函数即可,比如本例中的:

1'process_callbacks'=> array(
2        array($this,'toFloat'),
3      ),

返回的json范例

 1{
 2    "data": [
 3        {
 4            "id":1,
 5            "label":"The Beatles",
 6            "self":"http:\/\/example.com\/api\/v0.1\/testentity\/1",
 7            "customfieldname":"19.22"
 8        },
 9        {
10            "id":2,
11            "label":"Chuck Berry",
12            "self":"http:\/\/example.com\/api\/v0.1\/testentity\/2",
13            "customfieldname":"19.55"
14        }
15    ],
16    "count":2,
17    "self":{
18        "title":"Self",
19        "href":"http:\/\/example.com\/api\/v0.1\/testentity"
20    }
21}

自定义resource内容

有时候需要返回的json数据并不是单纯的输出entity,可能还需要经过汇总计算等等,这就只能自定义resource内容了。使用 controllersInfo()函数自定义控制器,然后在自定义function中输出任意array,Restful模组会帮你格式化成相应的json数据包含在data中输出。

Restful的过滤功能个人觉得参数传起来太麻烦了,比如这样子的:https://example.com/api/articles?filter[integer_multiple][value][0]=5&filter[integer_multiple][value][1]=10&filter[integer_multiple][operator][0]=">"&filter[integer_multiple][operator][0]="=" 一大长传太恶心了,好在自定义的function中可以直接用 $this->request->getParsedInput()来获取URL中传入的参数。

一个简单的范例,可以将上面的Testentity__1_0类改写为:

 1class Testentity__1_0 extends ResourceEntity implements ResourceInterface {
 2   /**
 3     * Override ResourceEntity::controllersInfo()
 4     * @return array
 5     */
 6    public function controllersInfo()
 7    {
 8        return array('^.*$' => array(
 9            RequestInterface::METHOD_GET => 'customviewEntity',
10        ));
11    }
12
13    public function customviewEntity($sid){
14        $input = $this->request->getParsedInput();
15        $query = $this->getDataProvider()->EFQObject();
16        if(!empty($input)){
17            $start_time = $input['start'];
18            $end_time = $input['end'];
19            $result = $query -> entityCondition('entity_type','sampleentitytype')
20                ->entityCondition('bundle','samplebundle')
21                ->propertyCondition('person_id',$sid)
22                ->propertyCondition('created',array($start_time,$end_time),'BETWEEN')
23                ->execute();
24        }else{
25            $result = $query -> entityCondition('entity_type','sampleentitytype')
26                ->entityCondition('bundle','samplebundle')
27                ->propertyCondition('person_id',$sid)
28                ->execute();
29        }
30        $jsonarray = array();
31        $sum = 0;
32        if(!empty($result)){
33            $e = entity_load('sampleentitytype',array_keys($result['sampleentitytype']));
34            foreach ($e as $entity) {
35                $sum += $entity->field_fp['und']['0']['value'];
36            }
37            $jsonarray = array('psersonid'=>$sid,'sum'=>$sum);
38        }
39        return $jsonarray;
40    }
41}

这样就能通过 http://example.com/testentity/$id?start=$starttime&end=$endtime 这样的URL来通过一段时间来过滤内容了,并且默认传$id的位置还能被定义成其他非数字字符,自由度很高。

小结

Restful 模组确实上手有点困难,相关内容杂七杂八并且重复性较高,多数时候还是只能直接读module的代码来去看它怎么用。另外1.x版本和2.x版本相也是很大的,要小心….


更多详情请参考 官方wiki 文档