リソースリンク

リソースは他のリソースをリンクすることができます。リンクには外部のリソースをリンクする外部リンク1と、リソース自身に他のリソースを埋め込む内部リンク2の2種類があります。

外部リンク

リンクをリンクの名前のrel(リレーション)とhrefで指定します。hrefには正規のURIの他にRFC6570 URIテンプレートを指定することができます。

    #[Link(rel: 'profile', href: '/profile{?id}')]
    public function onGet($id): static
    {
        $this->body = [
            'id' => 10
        ];
        return $this;
    }

上記の例ではhrefで表されていて、$body['id']{?id}にアサインされます。HALフォーマットでの出力は以下のようになります。

{
    "id": 10,
    "_links": {
        "self": {
            "href": "/test"
        },
        "profile": {
            "href": "/profile?id=10"
        }
    }
}

内部リンク

リソースは別のリソースを埋め込むことができます。#[Embed]srcでリソースを指定します。内部リンクされたリソースも他のリソースを内部リンクしているかもしれません。その場合また内部リンクのリソースが必要で、それが再帰的に繰り返されリソースグラフが得られます。

クライアントはリソースを何度もフェッチすることなく目的とするリソース群を一度に取得できます。3 例えば顧客リソースと商品リソースをそれぞれ呼び出す代わりに、注文リソースで両者を埋め込みます。

use BEAR\Resource\Annotation\Embed;

class News extends ResourceObject
{
    #[Embed(rel: 'sports', src: '/news/sports')]
    #[Embed(rel: 'weather', src: '/news/weather')]
    public function onGet(): static

埋め込まれるのはリソースリクエストです。レンダリングの時に実行されますが、その前にaddQuery()メソッドで引数を加えたりwithQuery()で引数を置き換えることができます。srcにはURI templateが利用でき、リクエストメソッドの引数がバインドされます(外部リンクと違って$bodyではありません)。

use BEAR\Resource\Annotation\Embed;

class News extends ResourceObject
{
    #[Embed(rel: 'website', src: '/website{?id}')]
    public function onGet(string $id): static
    {
        // ...
        $this->body['website']->addQuery(['title' => $title]); // 引数追加

セルフリンク

#[Embed]でリレーションを_selfとしてリンクすると、リンク先のリソース状態を自身のリソース状態にコピーします。

namespace MyVendor\Weekday\Resource\Page;

class Weekday extends ResourceObject
{
    #[Embed(rel: '_self', src: 'app://self/weekday{?year,month,day}')]
    public function onGet(string $id): static
    {

この例ではPageリソースがAppリソースのweekdayリソースの状態を自身にコピーしています。

HALでの内部リンク

HALレンダラーでは_embeddedとして扱われます。

リンクリクエスト

クライアントはハイパーリンクで接続されているリソースをリンクすることができます。

$blog = $this
    ->resource
    ->get
    ->uri('app://self/user')
    ->withQuery(['id' => 1])
    ->linkSelf("blog")
    ->eager
    ->request()
    ->body;

リンクは3種類あります。$relをキーにして元のリソースのbodyにリンク先のリソースが埋め込まれます。

  • linkSelf($rel) - リンク先と入れ替わります。
  • linkNew($rel) - リンク先のリソースがリンク元のリソースに追加されます
  • linkCrawl($rel) - リンクをクロールしてリソースグラフを作成します。

クロール

クロールはリスト(配列)になっているリソースを順番にリンクを辿り、複雑なリソースグラフを構成することができます。クローラーがWebページをクロールするように、リソースクライアントはハイパーリンクをクロールしてリソースグラフを生成します。

クロール例

author, post, meta, tag, tag/nameがそれぞれ関連づけられているリソースグラフを考えてみます。このリソースグラフに post-tree という名前を付け、それぞれのリソースの#[Link]アトリビュートでハイパーリファレンス href を指定します。

最初に起点となるauthorリソースにはpostリソースへのハイパーリンクがあります。1:nの関係です。

#[Link(crawl: "post-tree", rel: "post", href: "app://self/post?author_id={id}")]
public function onGet($id = null)

postリソースにはmetaリソースとtagリソースのハイパーリンクがあります。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)
{

tagリソースはIDだけでそのIDに対応するtag/nameリソースへのハイパーリンクがあります。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();

リソースクライアントは#[Link]アトリビュートに指定されたクロール名を発見するとそのrel名でリソースを接続してリソースグラフを作成します。

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',
                            ),
                        ),
                    ), 
                    // ...
  1. out-bound links 例)HTMLは関連した他のHTMLにリンクを張ることができます。 

  2. embedded links 例)HTMLは独立した画像リソースを埋め込むことができます。 

  3. DIで依存関係のツリーがグラフになっているオブジェクトグラフと同様です。