label305 / dug
为定义你的 Web 服务增添优雅
Requires (Dev)
- hamcrest/hamcrest-php: ^2.0
- mockery/mockery: 0.9.*
- phpunit/phpunit: ^5.4
This package is auto-updated.
Last update: 2024-08-29 04:19:53 UTC
README
用于有效地结合不同的数据源以创建可管理的 Web 服务响应。
实验性
这个库非常实验性,请在自己的风险下使用!
问题
在管理 RESTful API 时,你希望响应是一致的并且可组合的,例如获取一个用户
{
"id": 1,
"name": "John",
"friend_count": 30
}
这通常需要获取用户并添加 friend_count
。虽然对于单个用户来说这是可管理的,但当你想在其他对象中包含一个 user
对象时,你必须确保这些字段被包括以保持一致性。
随着你的 API 的增长,更难看到每个对象是如何组合的,这正是 Dug 发挥作用的地方。
Dug 允许你指向提供某些数据的来源,每个来源都可以相互引用并组合他们的数据。
基本用法
首先,我们设置一个 dug
$dug = new Dug();
然后注册你的第一个数据获取来源,它将从数据库中检索一些用户。注意,$path[1]
包含一个与 /[0-9]+/
匹配的结果数组,因此你可以组合查询。
$source = Source::build(['users', '/[0-9]+/'], function($path) {
$users = User::whereIn('id', $path[1])->get();
$result = [];
foreach($users as $user) {
$result[] = Data::build(
['users', $user->getId()],
[
'id' => $user->getId(),
'name' => $user->getName()
]
);
}
return $result;
});
要获取数据,你可以从一个来源请求数据
$dug->fetch(['users', [1,2]]);
这将导致以下数组
[
[
"id' => 1,
"name' => "Name of user 1",
],
[
"id' => 2,
"name' => "Name of user 2",
]
]
组合
让我们以基本用法的例子为基础,但在这个例子中我们添加了未读计数
$source = Source::build(['users', '/[0-9]+/'], function($path) {
$users = User::whereIn('id', $path[1])->get();
$result = [];
foreach($users as $user) {
$result[] = Data::build(
['users', $user->getId()],
[
'id' => $user->getId(),
'name' => $user->getName()
]
);
}
$unreadCounts = Counters::whereIn('user_id', $path[1])->get();
foreach($unreadCounts as $unreadCount) {
$result[] = Data::build(
['users', $unreadCount->getUserId()],
[
'unread_count' => $unreadCount->getValue()
]
);
}
return $result;
});
由于 Data::build
的第一个参数(来源)对于获取用户数据和获取未读计数都是相同的,例如 ['users', 1]
。Dug 知道它可以组合这些结果。
[
[
"id' => 1,
"name" => "Name of user 1",
"unread_count" => 123
],
[
"id' => 2,
"name" => "Name of user 2",
"unread_count" => 0
]
]
引用
现在假设我们有属于公司的用户,因此你将有一个名为 companies/1
的端点来获取单个公司,但你也将有一个名为 users/2
的端点来获取单个用户。由于每个用户只有一个公司,且公司的规范变化不大,你可能希望将公司直接包含在用户对象中,如下所示
[
"id" => 2,
"name" => "John",
"company" => {
"id" => 1,
...
}
]
现在出现了一个问题,你有一个在你的 API 中公司外观的定义,但你想在 companies/1
和 users/1
端点之间保持它们同步。这就是引用发挥作用的地方。
$source = Source::build(['users', '/[0-9]+/'], function($path) {
$users = User::whereIn('id', $path[1])->get();
$result = [];
foreach($users as $user) {
$result[] = Data::build(
['users', $user->getId()],
[
'id' => $user->getId(),
'name' => $user->getName(),
'company' => new ReferenceToSingle(['companies', $user->getCompany()])
]
}
return $result;
});
这假设你已经注册了另一个来源 ['companies', '/[0-9]+/']
,它将返回公司。Dug 将通过从这个来源获取数据并合并到用户对象中解决引用。
由于 Dug 首先合并所有数据,它还会合并所有引用以检查是否可以组合对 ['companies', '/[0-9]+/']
来源的调用,假设你获取了多个用户和公司。这意味着它只会对 ['companies', '/[0-9]+/']
来源进行一次往返!
依赖注入
当你的 API 增长时,你不想在闭包中完成所有事情,为此我们有 DataProvider
类。例如
class UserProvider implements DataProvider {
public function handle(array $path):array
//Your magic
}
}
你可以通过类名将其注册为一个来源
Source::build(['users', '/[0-9]+/'], UserProvider::class);
默认情况下,Dug 将简单地创建这个类的实例,但是如果你有一个依赖注入框架,你可能想使用它来初始化类并向构造函数注入内容。这可以通过 ClassInitializer
来完成
class LaravelClassInitializer implements ClassInitializer {
public function inititialize(string $className) {
return app($className);
}
}
$dug = new Dug();
$dug->setClassInitializer(new LaravelClassInitializer());
许可证
版权 2016 Label305 B.V.
根据 Apache 许可证 2.0 版(“许可证”);除非遵守本许可证,否则您不得使用此文件。您可以在以下位置获取许可证副本:
https://apache.ac.cn/licenses/LICENSE-2.0
除非适用法律要求或书面同意,否则根据本许可证分发的软件按“现状”分发,不提供任何形式的明示或暗示保证。有关许可证的权限和限制的具体语言,请参阅许可证。