bear / resource
面向对象服务的超媒体框架
Requires
- php: ^8.1
- ext-curl: *
- ext-filter: *
- justinrainbow/json-schema: ^5.2.10
- koriym/attributes: ^1.0
- koriym/http-constants: ^1.1
- koriym/json-schema-faker: ^0.2
- nocarrier/hal: ^0.9.12
- phpdocumentor/reflection-docblock: ^5.2
- psr/log: ^1.1 || ^2.0 || ^3.0
- ray/aop: ^2.12.3
- ray/di: ^2.13
- ray/web-param-module: ^2.1.1
- rize/uri-template: ^0.3
Requires (Dev)
- bear/devtools: ^1.0
- doctrine/coding-standard: ^12.0
- phpmd/phpmd: ^2.9
- phpmetrics/phpmetrics: ^2.7
- phpstan/phpstan: ^1.3
- phpunit/phpunit: ^9.5.10
- psalm/plugin-phpunit: ^0.13
- ray/compiler: ^1.9.1
- ray/rector-ray: ^1.0
- rector/rector: ^0.14.8
- squizlabs/php_codesniffer: ^3.5
- vimeo/psalm: ^4.17
- 1.x-dev
- 1.22.3
- 1.22.2
- 1.22.1
- 1.22.0
- 1.21.0
- 1.20.2
- 1.20.1
- 1.20.0
- 1.19.1
- 1.19.0
- 1.18.1
- 1.18.0
- 1.17.6
- 1.17.5
- 1.17.4
- 1.17.3
- 1.17.2
- 1.17.1
- 1.17.0
- 1.16.3
- 1.16.2
- 1.16.1
- 1.16.0
- 1.15.6
- 1.15.5
- 1.15.4
- 1.15.3
- 1.15.2
- 1.15.1
- 1.15.0
- 1.14.9
- 1.14.8
- 1.14.7
- 1.14.6
- 1.14.5
- 1.14.4
- 1.14.3
- 1.14.2
- 1.14.1
- 1.14.0
- 1.13.1
- 1.13.0
- 1.12.3
- 1.12.2
- 1.12.1
- 1.12.0
- 1.11.5
- 1.11.4
- 1.11.3
- 1.11.2
- 1.11.1
- 1.11.0
- 1.10.1
- 1.10.0
- 1.9.8
- 1.9.7
- 1.9.6
- 1.9.5
- 1.9.4
- 1.9.3
- 1.9.2
- 1.9.1
- 1.9.0
- 1.8.2
- 1.8.1
- 1.8.0
- 1.7.3
- 1.7.2
- 1.7.1
- 1.7.0
- 1.6.2
- 1.6.1
- 1.6.0
- 1.5.3
- 1.5.1
- 1.5.0
- 1.4.7
- 1.4.6
- 1.4.5
- 1.4.4
- 1.4.3
- 1.4.2
- 1.4.1
- 1.4.0
- 1.3.0
- 1.2.1
- 1.2.0
- 1.1.6
- 1.1.5
- 1.1.4
- 1.1.3
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
- 1.0.0-beta
- 1.0.0-alpha
- 0.x-dev
- 0.13.2
- 0.13.1
- 0.13.0
- 0.12.0
- 0.11.0
- 0.10.0
- 0.9.3
- 0.9.2
- 0.9.1
- 0.9.0
- 0.8.2
- 0.8.1
- 0.8.0
- 0.7.8
- 0.7.7
- 0.7.6
- 0.7.5
- 0.7.4
- 0.7.3
- 0.7.2
- 0.7.1
- 0.7.0
- 0.6.4
- 0.6.3
- 0.6.2
- 0.6.1
- 0.6.0
- 0.5.2
- dev-dependabot/composer/rector/rector-tw-1.2.4
- dev-InvokeRequestInterface
This package is auto-updated.
Last update: 2024-09-02 00:58:41 UTC
README
面向对象服务的超媒体框架
BEAR.Resource 是一个超媒体框架,允许资源表现得像对象。它允许对象具有RESTful网络服务的好处,例如客户端-服务器、统一接口、无状态、具有互连性和分层组件的资源表达式。
为了为现有的领域模型或应用程序数据引入灵活性和持久性,您可以通过在开发中将应用程序方法设置为REST中心,通过引入API作为您开发的驱动力来这样做。
资源对象
资源对象是具有资源行为的对象。
- 1个URI资源映射到1个类,它通过资源客户端检索。
- 向具有命名参数的方法发出请求,该方法响应统一资源请求。
- 通过请求,该方法更改资源状态并返回自身
$this
。
<?php namespace MyVendor\Sandbox\Blog; class Author extends ResourceObject { public $code = 200; public $headers = [ 'Content-Type' => 'application/json' ]; public $body = [ 'id' =>1, 'name' => 'koriym' ]; /** * @Link(rel="blog", href="app://self/blog/post?author_id={id}") */ public function onGet(int $id): static { return $this; } public function onPost(string $name): static { $this->code = 201; // created // ... return $this; } public function onPut(int $id, string $name): static { $this->code = 204; // no content //... return $this; } public function onDelete($id): static { $this->code = 204; // no content //... return $this; } }
实例检索
您可以通过使用解决依赖关系的注入器来检索客户端实例。
use BEAR\Resource\ResourceInterface; $resource = (new Injector(new ResourceModule('FakeVendor/Sandbox')))->getInstance(ResourceInterface::class);
通过任何一种方法,都可以提供解决URI(例如 app://self/user)到映射的 Sandbox\Resource\App\User 的资源客户端。
资源请求
使用URI和查询请求资源。
$user = $resource->get('app://self/user', ['id' => 1]);
- 此请求将1传递到符合 PSR0 的 Sandbox\Resource\App\User 类中的 onGet($id) 方法。
- 检索到的资源有3个属性 code、headers 和 body。
var_dump($user->body);
Array ( [name] => Athos [age] => 15 [blog_id] => 0 )
有两种请求方式。一种是 eager request
,另一种是 lazy reuqust
。
急切请求
急切请求是一种立即执行请求的方法。它与正常的PHP方法执行相同。有其他不同的编写方式,有一些原因。
$user = $resource ->get ->uri('app://self/user') ->withQuery(['id' => 1]) ->eager ->request();
$user = $resource->get->uri('app://self/user')(['id' => 1]);
$user = $resource->uri('app://self/user')(['id' => 1]); // 'get' request method can be omitted
延迟请求
正如延迟加载一样,直到需要时才请求资源。一个常见的用例是将 资源请求对象
分配给模板,而不是实例。
// get `ResourceRequest` objcet $user = $resource->get->uri('app://self/user')->withQuery(['id' => 1]); // assign to the template echo "User resource body is {$user}"; // same in the template enigne template
它是一个可调用对象,您可以使用其他参数调用它;
// invoke $user1 = $user(); // $id = 1 $user2 = $user(['id' => 2);
超媒体
资源可以包含指向其他相关资源的超链接。超链接通过带有 #[Link] 属性的方法显示。(PHP8.x)
use BEAR\Resource\Annotation\Link;
#[Link(rel: 'blog', href: 'app://self/blog?author_id={id}')]
或使用 @Link
注解。(PHP 7.x)
/** * @Link(rel="blog", href="app://self/blog?author_id={id}") */
关系名称由 rel 设置,链接URI由 href(超参考)设置。URI可以使用 URI模板(rfc6570)分配当前资源值。
在链接中,有几种类型 self、new、crawl 可以用来有效地创建资源图。
linkSelf
linkSelf
获取链接资源。
$blog = $resource ->get ->uri('app://self/user') ->withQuery(['id' => 0]) ->linkSelf('blog') ->eager ->request();
对 app://self/user 资源请求的结果跳过了 blog 链接,检索到 app://self/blog 资源。就像点击网页上的链接一样,它被下一个资源替换。
linkNew
linkNew
将链接资源添加到响应中。
$user = $resource ->get ->uri('app://self/user') ->withQuery(['id' => 0]) ->linkNew('blog') ->eager ->request(); $blog = $user['blog'];
在网页中,这就像“在新窗口中打开页面”,传递当前资源的同时也检索下一个资源。
爬取
爬取会遍历资源列表(数组),按顺序检索它们的链接,通过这种方式可以构建一个更复杂的资源图。就像爬虫爬取网页一样,资源客户端爬取超链接并创建资源图。
让我们考虑作者、帖子、元数据、标签、标签/名称,它们都通过资源图相互连接。每个资源都有一个超链接。在资源图中添加名称帖子树,在每个资源上添加超链接href在@link注释中。
在作者资源中有一个指向帖子资源的超链接。这是一个1:n的关系。
#[Link(rel: 'post-tree', href: 'app://self/post?author_id={id}')] public function onGet($id = null)
在帖子资源中有一个指向元数据和标签资源的超链接。这也是一个1:n的关系。
#[Link(crawl='post-tree', rel: 'meta', href: 'app://self/meta?post_id={id}')] #[Link(crawl='post-tree', rel: 'tag', href: 'app://self/tag?post_id={id}')] public function onGet($author_id) {
在标签资源中有一个超链接,只有一个与该ID对应的标签/名称资源的ID。这是一个1:1的关系。
#[Link(crawl='post-tree', rel: 'tag_name', href: 'app://self/tag/name?tag_id={tag_id}')] public function onGet($post_id)
设置爬取名称并发送请求。
$graph = $resource ->get ->uri('app://self/marshal/author') ->linkCrawl('post-tree') ->eager ->request();
资源客户端通过使用rel名称连接到带有@link注释的爬取名称,查找资源并创建资源图。
var_export($graph->body);
array (
0 =>
array (
'name' => 'Athos',
'post' =>
array (
0 =>
array (
'author_id' => '1',
'body' => 'Anna post #1',
'meta' =>
array (
0 =>
array (
'data' => 'meta 1',
),
),
'tag' =>
array (
0 =>
array (
'tag_name' =>
array (
0 =>
array (
'name' => 'zim',
),
),
),
...
HATEOAS(Hypermedia as the Engine of Application State)
资源客户端随后将下一个行为作为超链接,从该链接开始改变应用程序状态。例如,在订单资源中,通过使用POST创建订单,然后从订单状态到付款资源使用PUT方法进行付款。
订单资源
/** * @Link(rel="payment", href="app://self/payment{?order_id, credit_card_number, expires, name, amount}", method="put") */ public function onPost($drink)
客户端代码
$order = $resource ->post ->uri('app://self/order') ->withQuery(['drink' => 'latte']) ->eager ->request(); $payment = [ 'credit_card_number' => '123456789', 'expires' => '07/07', 'name' => 'Koriym', 'amount' => '4.00' ]; // Now use a hyperlink to pay $response = $resource->href('payment', $payment); echo $response->code; // 201
付款方法由订单资源通过超链接提供。尽管订单和付款之间的关系已改变,客户端代码没有任何变化,你可以在如何GET一杯咖啡了解更多关于HATEOAS的信息。
绑定参数
绑定参数
您可以将方法参数绑定到一个“外部值”。外部值可以是Web上下文或任何其他资源状态。
Web上下文参数
例如,而不是“拉取”$_GET
或任何全局Web上下文值,您可以绑定PHP超级全局值到方法参数。
use Ray\WebContextParam\Annotation\QueryParam;
class News extends ResourceObject
{
public function foo(#[QueryParam] string $id) : ResourceObject
{
// $id = $_GET['id'];
上面的例子是一个键名和参数名相同的情况。当它们不匹配时,您可以指定key
和param
值。
use Ray\WebContextParam\Annotation\CookieParam;
class News extends ResourceObject
{
#[CookieParam(key: 'id']
public function foo(string $tokenId) : ResourceObject
{
// $tokenId = $_COOKIE['id'];
完整列表
use Ray\WebContextParam\Annotation\QueryParam; use Ray\WebContextParam\Annotation\CookieParam; use Ray\WebContextParam\Annotation\EnvParam; use Ray\WebContextParam\Annotation\FormParam; use Ray\WebContextParam\Annotation\ServerParam; class News extends ResourceObject { public function onGet( #[QueryParam('use_id')] string $userId, // $_GET['use_id']; #[CookieParam('id')] string $tokenId, // $_COOKIE['id'] or "0000" when unset; #[EnvParam('app_mode')] string $app_mode, // $_ENV['app_mode']; #[FormParam('token')] string $token, // $_POST['token']; #[ServerParam('SERVER_NAME') #server // $_SERVER['SERVER_NAME']; ) : ResourceObject {
此绑定参数
对于测试也非常有用。
资源参数
我们可以使用@ResourceParam
注释将另一个资源的状态绑定到一个参数上。
use BEAR\Resource\Annotation\ResourceParam;
class News extends ResourceObject
{
/**
* @ResourceParam(param=“name”, uri="app://self//login#nickname")
*/
public function onGet(string $name) : ResourceObject
{
在这个例子中,app://self//login
的nickname
属性绑定到$name
。
资源表示
每个资源都有一个用于表示的渲染器。这个渲染器是资源的依赖项,因此它通过注入器进行注入。除了JsonModule
之外,您还可以使用HalModule
,它使用HAL(Hyper Application Laungage)渲染器。
$modules = [new ResourceModule('MyVendor\Sandbox'), new JsonModule]: $resource = Injector::create(modules) ->getInstance('BEAR\Resource\ResourceInterface');
当资源以字符串形式输出时,注入的资源渲染器将被使用,然后显示为资源表示。
echo $user; // { // "name": "Aramis", // "age": 16, // "blog_id": 1 // }
在这种情况下,$user
是渲染器内部的ResourceObject
。这不是一个字符串,所以它被当作数组或对象处理。
echo $user['name']; // Aramis echo $user->onGet(2); // { // "name": "Yumi", // "age": 15, // "blog_id": 2 // }
延迟加载
$user = $resource ->get ->uri('app://self/user') ->withQuery(['id' => 1]) ->request(); $smarty->assign('user', $user);
在非eager
request()
中,不是资源请求结果,而是一个请求对象被检索。当它在资源请求输出时的模板中分配给模板引擎时,即模板中的{$user}
,执行资源请求和资源渲染,并显示为字符串。
嵌入资源
@Embed
注解使得将外部资源嵌入变得更加容易。例如,在 HTML 中使用 <img src="image_url">
或 <iframe src="content_url">
,嵌入的资源通过 src
字段指定。
class News extends ResourceObject { #[Embed(rel: 'weather', src: 'app://self/weather/today')] public function onGet(): static { $this->body = [ 'headline' => "...", 'sports'] = "..." ]; return $this; } }
weather
资源就像在这个 News
资源中的 headline
或 sports
一样被嵌入。
HAL (Hypertext Application Language)
HAL Module
将资源表示改为 HAL。
当嵌入的资源存在时进行评估。
// create resource client with HalModule $resource = (new Injector(new HalModule(new ResourceModule('FakeVendor\Sandbox'))))->getInstance(ResourceInterface::class); // request $news = $resource ->get ->uri('app://self/news') ->withQuery(['date' => 'today']) ->request(); // output echo $news . PHP_EOL;
结果
"headline": "40th anniversary of Rubik's Cube invention.", "sports": "Pieter Weening wins Giro d'Italia.", "_links": { "self": { "href": "/api/news?date=today" } }, "_embedded": { "weather": [ { "today": "the weather of today is sunny", "_links": { "self": { "href": "/api/weather?date=today" }, "tomorrow": { "href": "/api/weather/tomorrow" } } } ] } }
一个演示应用程序代码可在 此处 获取。
表示
将 ResourceObject
转换为字符串以获取资源视图。
$userView = (string) $resource->get('app://self/user?id=1'); echo $userView; // get JSON
您可以通过注入渲染器来更改视图格式(媒体类型)。以下示例说明了如何将简单的 JSON 表示渲染器“注入”到构造函数中。通常是通过依赖注入库进行注入。
class User extends ResourceObject { public function __construct() { $this->setRenderer(new class implements RenderInterface{ public function render(ResourceObject $ro) { $ro->headers['content-type'] = 'application/json'; $ro->view = json_encode($ro->body); return $ro->view; } }); } }
传输
REST 表示“表示状态传输”。ResourceObject
中的 transfer()
方法将资源视图输出到客户端。
$user = $resource->get('app://self/user?id=1'); $user->transfer(new class implements TransferInterface { public function __invoke(ResourceObject $ro, array $server) { foreach ($ro->headers as $label => $value) { header("{$label}: {$value}", false); } http_response_code($ro->code); echo $ro->view; } );
以上是一个简单的 HTTP 响应传输示例。在不同的环境中(如“控制台”、“流”或“套接字服务器”)可能会进行更改。
性能提升
资源客户端是可序列化的,具有巨大的性能提升。在生产环境中推荐使用。
use BEAR\Resource\ResourceInterface; // save $resource = (new Injector(new ResourceModule('FakeVendor/Sandbox')))->getInstance(ResourceInterface::class); $cachedResource = serialize($resource); // load $resource = unserialize($cachedResource); $news = $resource->get('app://self/news');
注解/属性
BEAR.Resource 可以与 PHP 7/8 的 doctrine/annotation 或 PHP8 的 Attributes 一起使用。请参阅旧版 README(v1.4) 中的注解代码示例。
安装
composer require bear/resource
面向资源的框架
BEAR.Sunday 是一个面向资源的框架。在 BEAR.Sunday 中,除了 BEAR.Resource 之上的网络行为之外,还增加了 Google guice 风格的 DI/AOP 系统 Ray,并且是一个网络应用程序框架。
请访问 BEAR.Sunday 网站。
另请参阅
- BEAR.QueryRepository - 将读取和写入分离到两个独立的存储库中。
- Ray.WebParamModule - 将网络上下文的值绑定到方法参数。
测试 BEAR.Resource
以下是安装 BEAR.Resource 的源代码并运行单元测试和演示的步骤。
composer create-project bear/resource BEAR.Resource
cd BEAR.Resource
./vendor/bin/phpunit
php demo/run.php