Drupal7 中Restful模组应用简单介绍

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

安装并启用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

模组文件结构

custom_restfulapi/
custom_restfulapi/custom_restfulapi.info
custom_restfulapi/custom_restfulapi.module
custom_restfulapi/src/
custom_restfulapi/src/Plugin/
custom_restfulapi/src/Plugin/resource/
custom_restfulapi/src/Plugin/resource/entity/
custom_restfulapi/src/Plugin/resource/entity/Testentity__1_0.php
...

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

name = RESTful custom
description = Custom RESTful resource.
core = 7.x
dependencies[] = restful

registry_autoload[] = PSR-4

建立API端点(API endpoint)

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

<?php

namespace Drupal\custom_restfulapi\Plugin\resource\entity;

use Drupal\restful\Plugin\resource\ResourceEntity;
use Drupal\restful\Plugin\resource\ResourceInterface;

/**
 * Class Testentity__1_0
 * @package Drupal\custom_restfulapi\Plugin\resource\entity
 *
 * @Resource(
 *   name = "Testentity:1.0",
 *   description = "A simple testapi.",
 *   resource = "testentity",
 *   label = "Test entity ",
 *   authenticationTypes = {
 *   "token"
 *   },
 *   authenticationOptional = TRUE,
 *   dataProvider = {
 *     "entityType": "sampleentitytype",
 *     "bundles": {
 *       "samplebundle","samplebundle2"
 *     },
 *   },
 *   majorVersion = 1,
 *   minorVersion = 0
 * )
 */

class Testentity__1_0 extends ResourceEntity implements ResourceInterface
{
    /**
     * Overrides ResourceEntity::publicFields().
     * @return array
     */
    protected function publicFields()
    {
        $public_fields = parent::publicFields();
        $public_fields['customfieldname'] = array(
            'property'=>'field_number',
            'process_callbacks' => array(
                array($this,'toFloat'),
            ),
        );
        return $public_fields;
    }

    /**
     * @param $value
     * @return float
     */
    public function toFloat($value){
        $value = (float)$value;
        return $value;
    }

}

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编码后的字符串;
    • 响应:
{
  "access_token": "D8uD2xjDtlT8ym0BiM-n4R-cssRACxgf34xsBQpgGKw",
  "type": "Bearer",
  "expires_in": 86400,
  "refresh_token": "jhHpCr3FubMsSyE-9B6wPgtPMeQn2u34-P4blDSiXrw"
}
  • authenticationOptional若值为TRUE则不验证,直接可以获取资源,这个设置在开发和测试的时候很好用;
  • dataProvider定义了数据来源,具体到了bundle,可以多个bundle;
  • majorVersionminorVersion 定义了版本号。

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

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

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

'process_callbacks'=> array(
        array($this,'toFloat'),
      ),

返回的json范例

{
    "data": [
        {
            "id":1,
            "label":"The Beatles",
            "self":"http:\/\/example.com\/api\/v0.1\/testentity\/1",
            "customfieldname":"19.22"
        },
        {
            "id":2,
            "label":"Chuck Berry",
            "self":"http:\/\/example.com\/api\/v0.1\/testentity\/2",
            "customfieldname":"19.55"
        }
    ],
    "count":2,
    "self":{
        "title":"Self",
        "href":"http:\/\/example.com\/api\/v0.1\/testentity"
    }
}

自定义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类改写为:

class Testentity__1_0 extends ResourceEntity implements ResourceInterface {
   /**
     * Override ResourceEntity::controllersInfo()
     * @return array
     */
    public function controllersInfo()
    {
        return array('^.*$' => array(
            RequestInterface::METHOD_GET => 'customviewEntity',
        ));
    }

    public function customviewEntity($sid){
        $input = $this->request->getParsedInput();
        $query = $this->getDataProvider()->EFQObject();
        if(!empty($input)){
            $start_time = $input['start'];
            $end_time = $input['end'];
            $result = $query -> entityCondition('entity_type','sampleentitytype')
                ->entityCondition('bundle','samplebundle')
                ->propertyCondition('person_id',$sid)
                ->propertyCondition('created',array($start_time,$end_time),'BETWEEN')
                ->execute();
        }else{
            $result = $query -> entityCondition('entity_type','sampleentitytype')
                ->entityCondition('bundle','samplebundle')
                ->propertyCondition('person_id',$sid)
                ->execute();
        }
        $jsonarray = array();
        $sum = 0;
        if(!empty($result)){
            $e = entity_load('sampleentitytype',array_keys($result['sampleentitytype']));
            foreach ($e as $entity) {
                $sum += $entity->field_fp['und']['0']['value'];
            }
            $jsonarray = array('psersonid'=>$sid,'sum'=>$sum);
        }
        return $jsonarray;
    }
}

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

小结

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

Y Cheung

Y Cheung

Blogger, Programer & Traveler.
Shanghai