atk4/api

敏捷API - PHP中的可扩展API服务器,用于敏捷数据

维护者

详细信息

github.com/atk4/api

主页

源码

问题

0.3 2020-02-11 22:09 UTC

README

Build Status StyleCI codecov Code Climate Issue Count

License GitHub release

端到端实现您的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 CountryPersistence $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';
   });
});

您还可以重定向