轻松创建RESTful API和服务。基于Klein.php

v0.13 2014-04-04 21:52 UTC

This package is not auto-updated.

Last update: 2024-09-24 06:17:25 UTC


README

Tagged/Rest是一个小型框架,用于在面向对象的控制器中创建RESTful API,而不是回调。它包含了来自json schema项目的强大验证,API方法既可以响应HTTP请求,也可以作为普通对象方法内部调用。还有一个机制可以免费动态生成所有API端点的文档,只需用OPTIONS请求击中URL即可。

该框架只关注处理HTTP请求,将项目的其余部分留给其他框架或现有代码。

实际上,这只是一个围绕Klein的包装器,允许轻松构建RESTful URL和控制器。

URL基于路由映射映射到控制器,控制器处理HTTP请求/响应。

路由

一个路由看起来像这样的/owners/[a:name]/dogs

方括号中的值将被匹配并作为查询参数传递给控制器。

对上述路由的请求/owners/george/dogs?color=brown将变为

name = george
color = brown

作为URL路径最后一部分的参数的路由被认为是资源路由,而以静态名称作为最后一部分的路由是集合。在上面的例子中,dogs路由是一个集合,而photos是一个资源。

路由支持许多功能,例如可选值和验证。有关路由语法的更多信息,请参阅https://github.com/chriso/klein.php#routing

路由器接受一个参数,该参数是一个路由到控制器的数组映射,如下所示

$routes => array(
            "/login" => "login",
            "/health" => "health",
            # the scope operator allows for setting a specific method
            "/health/echo" => "health::showParams",
            #  a namespace URL. Children of this are appended.
            "/users/[i:userid]" => array(
                "/loginhistory" => "loginhistory",
                "/messages" => "messages",
                "/photos" => "photos",
                "/photos/[i:photoid]" => "photos"
            )
        )

其中键是路由路径,值是命名空间。如果值是一个字符串,则该路由在父路径下嵌套。路由器还接受一个参数定义路由命名空间,另一个参数定义控制器命名空间。

一种特殊语法允许将特定方法映射到该路由,而不是默认方法。如果右侧看起来像"controllerClass::methodName",则左侧路由的POST请求将由该方法处理。在上面的例子中,对/health/echo的POST请求将由健康控制器中的'showParams()'方法处理。

以下是一个使用上述路由数组的示例。如果路由命名空间为/api/v1,控制器命名空间为/app/controllers,则上述配置生成的路由如下所示

/api/v1/login                                => /app/controllers/login.php
/api/v1/users/[i:userid]/loginhistory        => /app/controllers/loginhistory.php
/api/v1/users/[i:userid]/messages            => /app/controllers/messages.php
/api/v1/users/[i:userid]/photos              => /app/controllers/photos.php
/api/v1/users/[i:userid]/photos/[i:photoid]  => /app/controllers/photos.php

每个路由支持HTTP方法由查看控制器确定。路由可以是集合处理,也可以是资源处理。资源对单个记录(数据库行)执行操作,而集合对多个记录(数据库表)执行操作。在上面的例子中,对/api/v1/users/[i:userid]/photos的请求将由控制器作为集合处理,而/api/v1/users/[i:userid]/photos/[i:photoid]作为资源处理。这是通过资源路由惯例(以id/参数结束/[i:photoid])和集合惯例(以集合名称结束/photos)确定的。

验证

验证使用json模式验证器完成。查询/表单参数被视为嵌套的json结构。这种结构来自PHP内置的查询反序列化。像'?item[key]=value'这样的查询参数序列化为数组,如array( 'item' => array('key' => 'value'))

这被输入到验证器中,确保查询参数的结构符合API的预期。有关json模式语法和选项的更多信息,请查看https://json-schema.fullstack.org.cn/

每个控制器函数的模式都在构造函数中注册。

控制器

控制器处理HTTP请求。处理方法期望传入一个stdClass对象。方法返回的值成为HTTP请求的主体。

默认映射如下,但可以覆盖。未实现的方法在请求时将返回HTTP 405错误。

每个控制器方法都传入一个响应和请求对象。请查看https://github.com/chriso/klein.php/wiki/Api以获取文档。

请注意,对于集合的GET请求有两个处理器。这样做只是为了使控制器方法有更好的名称。当方法将返回数据集合时,请使用find。Index更适合响应基本的URL,如健康检查或HTML页面。

控制器可以注册任意多的方法来响应相同的HTTP操作,这对于自定义操作非常有用。自定义方法可以在构造函数中注册。此外,多个控制器可以响应相同的路由。

控制器示例

<?php
class api_v2_photos extends \Tagged\Rest\Api\Base {
    public function __construct() {
        $this->_registerInputSchema( "find", array(
            "title" => "Query for user's uploaded photos by date, paginated",
            "type" => "object",
            "properties" => array(
                "dateRange" => \Tagged\Rest\Schema::DateRange(),
                "pagination" => \Tagged\Rest\Schema::Pagination(array(
                        "defaultPage"=>1,
                        "defaultSize"=>2,
                )),
            ),
            "required"=>array('pageNumber','pageSize')
        ));
        
        $this->_registerInputSchema( "fetch", array(
            "title" => "Query for a specific uploaded photo",
            "type" => "object",
            "properties" => array(
                "userid"=> array(
                    "type"=>"number",
                ),
                "photoid"=> array(
                    "type"=>"number",
                    "description"=>"The id of the image to retreive"
                ),
            ),
            "required"=>array('userid','imageid')
        ));
    }
    
    public function find($request) {
        $dao = new tag_dao_photo();
        list($start,$end) = \Kleinbottle\Schema\DateRange::getDates($request->dateRange);

        $photoset = $dao->getPhotosByDate(
            $start,
            $end,
            $request->pageNumber,
            $request->pageSize
        );
        
        return array(
            "size" => count($photoset),
            "photos" => $photoset,
        )
    }
    
    public function fetch($request) {
        $uid = $request->userid;
        $pid = $request->photoid;
        
        $dao = new tag_dao_photo();
        
        return $dao->getPhoto(
            $uid,
            $pid
        );
    }
}

此控制器的示例URL为domain.com/api/v1/users/5/photos?pagination[page]=2&pagination[size]=25

注意表单参数,这是验证器将验证的结构。请参阅验证。

控制器可以添加一个后响应方法来格式化输出。实现_formatOutput的控制器能够提供不同格式的输出。格式来自URL的扩展名。例如,URL www.mysite.com/page.html 将添加查询参数 $format='html',而 www.mysite.com/page.html 将添加查询参数 $format='json'。如果没有提供格式,则值为null。

<?php
protected function _formatOutput($method, $data, $format) {
  if($method === 'fetch') {
    switch($format) {
      case 'html':
        return tag_page::render('some/template/path',$data);
      default:
        return json_encode($data);
    }
  }
}

用法

将其放在index.php文件中

<?php
$urlRoot = '/api/v1';
$routes = array(
    "/login" => "login",
    "/users/[i:userid]" => array(
        "/loginhistory" => "loginhistory",
        "/messages" => "messages",
        "/photos" => "photos",
        "/photos/[i:photoid]" => "photos"
    )
);
$controllerPrefix = "tag_admin_api_v1";

$router = new tag_routing_core(
    $urlRoot,
    $routes,
    $controllerPrefix
);

//it's probably a good idea to cache this.
//building the routes is long
$router->buildRoutes();

//defualt uses $_SERVER vars
$router->routeRequest();

//Or specify directly (useful for manual driving and tests)
$router->routeRequest($_SERVER['REQUEST_URI'], $_SERVER['HTTP_METHOD]);

最好将所有这些放入配置文件并在索引文件中加载它们。

内部调用API方法

所有API方法都可以在不发出HTTP请求的情况下调用。有两种方法可以直接调用和包装。当包装时,将应用过滤器和服务端点验证,并且可以使用数组而不是stdClass对象调用方法。

包装时,这将经历与Web请求相同的过滤。

$photoApi = api_v2_photos::raw();

// use the same structure as an api call
$photoObj = $photoApi->fetch(array(
  'userid'=>5,
  'photoid'=>4216123
));

当然,另一种方法是实例化对象并以正常对象的方式调用方法。直接调用方法有点糟糕,因为所有控制器都期望stdClass对象。

自动API文档

对于API来说,文档很重要,这个框架提供了一个机制来自动生成免费的文档。

对任何API发起OPTIONS请求将返回包含该API文档的HTML页面。文档来自类的doc注释以及为API方法指定的模式。

计划添加一个端点,作为此类文档的集合,这样所有API都很容易被发现。

由于文档是动态生成的,它将始终与代码匹配,而不是需要运行作业来保持文档和API同步。

注意

此项目尚未进行性能优化,但应与Klein类似运行。大部分工作是在将路由映射到控制器回调以及构建路由器后完成的,之后基本上就是一个Klein项目。

目前,对于一些生产API,我们每次请求都会构建路由,大约有60毫秒的额外开销。对于大规模服务,这是你需要缓存的地方。缓存不是内置的,但路由器设计得可以很容易地缓存,如果需要的话。

依赖项

Klein(用于路由)https://github.com/chriso/klein.php geraintluff的强制JSON验证器 https://github.com/geraintluff/jsv4-php

进行测试需要Phockito和php-unit。