atk4 / api
敏捷API - PHP中的可扩展API服务器,用于敏捷数据
Requires
- php: >=7.2.0
- atk4/data: ^2.0
- zendframework/zend-diactoros: ^1.6
Requires (Dev)
This package is auto-updated.
Last update: 2024-09-13 22:16:33 UTC
README
端到端实现您的RESTful API和RPC。为已经使用敏捷数据的应用程序提供非常简单的方式来定义API端点。
1. 简单易用
敏捷API力求非常简单且即开即用。以下是启动基本API的最小代码,将其放入v1.php
文件,然后调用composer require Atk4/Api
include 'vendor/autoload.php'; $api = new \Atk4\Api\Api(); // Simple handling of GET request through a callback. $api->get('/ping', function() { return 'Pong'; }); // Methods can accept arguments, and everything is type-safe. $api->get('/hello/:name', function ($name) { return "Hello, $name"; });
2. 敏捷数据集成
敏捷数据是一个数据持久化框架。简单来说,您可以使用敏捷数据创建业务模型(实体)并与数据库交互。如果已经在敏捷数据中定义了类和持久化,敏捷API将是一个完美的集成。以下代码假设您有Model Country
和Persistence $db
$api->rest('/countries', new Country($db));
这为客户端模型创建了一个符合标准的RESTful接口
GET /countries
返回来自$db的所有国家记录列表。POST /countries
从表单数据或POST正文中读取数据以添加新国家。GET /countries/123
加载指定ID的客户端。- 带有一些表单数据或JSON的
PATCH /countries/123
将更新现有的国家。 DELETE /countries/123
将删除一条记录。
通过敏捷UI,您可以添加条件、限制等。第二个参数也可以是一个回调
$api->rest('/countries', function() use($db) { $c = new Country($db)); $c->addCondition('is_eu', true); $c->setLimit(20); return $c; });
字段类型、数据转换、验证和钩子都可以通过敏捷数据定义,使API层非常透明且简单。如果您尝试加载不存在的记录,API将返回404。其他错误将正确映射到API代码或回退到500。
进行中
敏捷UI仍在进行中。此readme将进一步更新以反映当前功能。
计划中的功能
敏捷API正在开发中,但以下功能是计划中的
-
简单易用。
-
模型路由。通过将其与模型关联来提供端点。
-
全局身份验证。为整个框架提供身份验证策略。
-
支持速率限制。按账户、按IP计数器,可以存储在MemCache或Redis中。
-
深度日志记录,与数据持久化集成。不仅存储请求,还存储持久化中受影响的数据。
-
支持API UNDO。抵消API调用对后端的影响。
简单易用
要设置您的API,只需创建新的RestAPI类实例并定义路由。您可以通过创建“v1”文件夹并将index.php
放入该文件夹来启用版本控制。有些事情已经工作得很好,我们不想重新发明它们!
require 'vendor/autoload.php'; $app = new \Atk4\Api\Api(); $db = \Atk4\Data\Persistence::connect($DSN); // Lets set our index page $app->get('/', function() { return 'This worked!'; }); // Getting access to POST data $app->post('/stats/:id', function($id, $data) { return ['Received POST', 'id'=>$id, 'post_data'=>$data] });
使用函数调用回调调用方法,如get()
、post()
,将注册它们,如果URL与模式匹配,则将执行所有匹配的回调,即,直到其中一些返回值。
执行将在匹配确认后立即发生(有助于错误显示)。
技术上这允许匹配多个回调
$app->get('/:method', function($method) { // do something }); $app->get('/ping', function() { return 'pong'; });
注意,一些流行的PHP API框架(如Slim)使用{name}进行参数匹配,然而IT行业的其余部分更喜欢使用":name"。我们将使用行业模式匹配,但也将尝试支持{$foo},尽管它看起来与敏捷UI模板标签太相似。
我认为这些方法可以巧妙地匹配规则
function get($route, $action) { if ($_SERVER['REQUEST_METHOD'] == 'GET') { return $this->match($route, $action); } }
关于match
的一个有用提示是,它可以不执行任何操作而返回true
/false
。
if ($app->match('/misc/**')) { // .. execute logic for requests starting with /misc/... } else { // .. other logic }
模型路由
rest()
方法实现了一个针对模型的标准RESTful API端点。有两种使用方法
$app->rest('/clients', new Client($db));
这将为访问模型启用所有必要的操作,特别是
- GET /clients - 列出所有客户
- GET /clients/:id - 获取特定客户数据
- POST /clients - 创建新客户
- PUT /clients/:id - 与patch相同
- PATCH /clients/:id - 加载、仅更新指定字段、保存
- DELETE /clients/:id - 删除特定客户记录
如果您不想使用主键,也可以指定不同的字段
$app->rest('/country/:iso_name');
Agile Data提供了强大的遍历引用的方法,上述方法也可以使用
$app->rest('/clients/:id/orders/::Orders:id', new Client($db));
这将为诸如/clients/123/orders/395
之类的URL创建新的路由。首先加载ID为123的客户模型,然后执行ref('Orders')。其余的逻辑与之前类似。
这也为我们提供了执行深度遍历的选项
$app->rest('/clients/:id/order_payments/::Orders::Payments:id', new Client($db));
这将加载客户,执行ref('Orders')->ref('Payments')。最后的"id"是可选的
$app->rest('/client/:/order_payments/::Orders::Payments', new Client($db));
有时您可能希望有更多的控制权,因此您可以使用
$app->rest('/client/:id/invoices-due', function($id) use($db) { $client = new Client($db); $client->load($id); return $client->ref('Invoices')->addCondition('status', 'due'); });
rest()
方法建立在put()
、get()
、post()
和其他方法之上。方法rest()
的第三个参数可以指定一个包含选项的数组。
身份验证
我们的API支持各种身份验证方法。其中一些是内置的,第三方扩展也可以使用。
让我们看看最基本的用户/密码身份验证。
// Enable user/password authentication. Field values are optional $app->userAuth('/**', new User($db));
您可以战略性地放置身份验证方法,它将保护所有后续的路由,但不会保护其上的路由。您还可以使用自定义路由,如果只想保护API的一部分。
AUTH方法将查找HTTP_AUTH头,如果无法使用相应的用户/密码组合加载用户记录,将返回405代码。
用户身份验证执行后,$app->user
将存在
$app->authUser('/**', new User($db)); $app->rest('/notifications', $app->user->ref('Notifications'));
速率限制
速率限制支持将限制用户(或IP)可以进行的请求数量。它很容易设置
$app->authUser('/**', new User($db)); $limit = new \Atk4\Api\Limit($db); $limit->addCondition('user_id', $app->user->id); $app->get('/limits', function() use ($limit){ return $limit; }); $app->rateLimit('/**', $limit, 10); // 10 requests per minute $app->rest('/notifications', $app->user->ref('Notifications'));
建议使用Redis或Memcache之类的持久性速率限制
$cache = \Atk4\Data\Persistence\MemCache($conn); $limit = new \Atk4\Api\Limit($cache);
深度日志记录
Agile Data已经支持审计日志,但通过Agile API可以进一步补充
$audit_id = $app->auditLog( '/**', new \Atk4\Audit\Controller( new \Atk4\Audit\Model\AuditLog($db) ) );
这将为每个调用创建一个日志条目,并用于数据持久性中的所有后续更改。
请注意,上述函数生成的$audit_id
也可以用于UNDO操作
$app->auditLog->load($audit_id)->undo();
这将撤销持久层上所做的所有更改。
错误日志记录
类似于Agile UI,API应用程序将捕获抛出的异常。
$app->?
系统支持和全局作用域
Agile Data支持全局作用域,因此您可以添加额外的钩子,该钩子将影响所有模型的创建并添加一些额外的条件。这在基于身份验证响应的情况下很有用
$user_id = $app->authUser('/**', new User($db)); $db->addHook('afterAdd', function($o, $e) use ($user_id) { if ($e->hasElement('user_id')) { $e->addCondition('user_id', $user_id); } })
映射到文件系统
$app->map('/:resource/**', function(resource) use($app) { // convert user-credit to UserCredit $class = preg_replace('/[^a-zA-Z]/', '', ucwords($resoprce)); $object = $app->factory($class, null, 'Interface'); // Interface\UserCredit.php return [$object, $app->method]; // convert path to file // load file // create class instance // call method of that class // TODO: think of some logical example here!! });
可选参数
Agile API支持各种GET参数。
?sort=name,-age
指定要排序的列。?q=search
,将尝试通过短语执行全文搜索。(如果持久性支持)?condition[name]=value
,条件,但也可以使用?name=value
?limit=20
,一次返回20个结果。?skip=20
,跳过前20个结果。?only=name,surname
指定仅字段?ad={transformation}
,应用Agile Data转换
这些参数的处理发生在函数args()
内部。它传递给一个模型,因此它将查看GET参数并执行必要的更改。
function args(\Atk4\Data\Model $m) { if ($_GET['sort']) { $m->sortBy($_GET['sort']); } if ($_GET['condition']) { foreach($_GET['condition'] as $key=>$val) { $m->addCondition($key, $val); } } if ($_GET['limit'] || $_GET['skip']) { $m->setLimit($_GET['limit']?:null, $_GET['skip']?:null); } // etc. etc... }
其他要点
Agile API仅支持JSON。您可能能够添加XML输出,但为什么。
敏捷API不使用信封结构。对于空结果,响应数据将为"[]"。如果响应存在问题,您将通过状态码获得,此时输出将发生变化。
敏捷API不支持HATEOAS。从技术上讲,您应该能够添加对其的支持,但这可能需要更复杂的映射或额外的代码。我们更喜欢保持简单。
敏捷API默认将JSON格式化输出,因此请确保已启用“gzip”。
敏捷API接受原始JSON或表单编码的输入,但示例始终使用JSON。
敏捷API不使用“分页”,而是使用“limit”和“skip”值。如果您愿意,可以引入页面。
深度加载资源是您可以添加的功能。例如,如果您加载“发票”,它可能包含包含列表的“lines”数组。将提供有关如何实现此功能的文档。还将有一个参数来指示是否需要深度加载。
错误和异常将包含“error”、“message”和“args”键。可选键“raised_by”可能包含具有相同键的另一个对象,如果错误由另一个错误引发。另一种可能性是“description”字段。
(见http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api)
https://www.reddit.com/r/PHP/comments/32tbxs/looking_for_php_rest_api_framework/
测试/ behat: http://restler3.luracast.com/examples/_001_helloworld/readme.html
URL模式
这里有一些示例
/user/:id
匹配 /user/123 , /user/123/ , /user/abc/ 但不会匹配 /user/123/x/user/:
与上面相同/user/:/:
匹配 /user/123/321 但不会匹配 /user/123/user/*/:
匹配 /user/blah/123 但会忽略blah/user/**/:
错误,因为**
必须是最后一个。/user/:/**
匹配 /user/123/blah 和 /user/123/foo/blah 和 /user/123/user/:id/:action?
可选参数。如果未指定,则为null
路由组
可以将路由组重定向到不同的应用。
$app = new \Atk4\Ui\App\Api(); $app->group('/user/**', function($app2) { $app2->get('/test', function() { return 'yes'; }); });
您还可以重定向