标签化 / REST
轻松创建RESTful API和服务。基于Klein.php
Requires
- php: >=5.3.0
- geraintluff/jsv4: dev-master
- klein/klein: dev-master
Requires (Dev)
- hafriedlander/phockito: dev-master
- phpunit/phpunit: 3.7.x
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。