API Platform Adminのリレーション設定

API Platform Adminのdemoをベースに管理画面を作成していたところ、リレーションされてエンティティのID以外拾ってくれないという厄介な現象に遭遇しました。

dataProviderのuseEmbeddedという設定がfalseになっていたことが原因でした。ドキュメントちゃんと読まないとダメですね。

以下ドキュメント翻訳です。

Handling Relations

API Platform Admin は、一対一および対多の関係を自動的に処理します。

Schema.org サポートのおかげで、IRI の代わりに関連リソースの名前を簡単に表示できます。

Embedded Relations 埋め込まれた関係

リレーションが埋め込みの配列または埋め込みリソースである場合、Adminはデフォルトでそれらを保持します。

埋め込まれたデータはテキスト フィールドとして表示され、テキスト入力として編集可能です。Adminはそこに存在するフィールドを特定できません。

必要なフィールドを表示するには、このセクションを参照してください。

また、Hydra データ プロバイダーの useEmbedded パラメータを false に設定することにより、埋め込まれたリソースのデータを IRI で自動的に置き換えるようにAdminに依頼することもできます。埋め込みデータはローカル キャッシュに挿入されます。後で埋め込みリソースのいくつかのフィールドを参照する場合、追加のリクエストを行う必要はありません。

// admin/src/App.js

import { HydraAdmin, fetchHydra, hydraDataProvider } from "@api-platform/admin";
import { parseHydraDocumentation } from "@api-platform/api-doc-parser";

const entrypoint = process.env.REACT_APP_API_ENTRYPOINT;

const dataProvider = hydraDataProvider({
    entrypoint,
    httpClient: fetchHydra,
    apiDocumentationParser: parseHydraDocumentation,
    mercure: true,
    useEmbedded: false,
});

export default () => (
  <HydraAdmin
      dataProvider={dataProvider}
      entrypoint={entrypoint}
  />
);

Display a Field of an Embedded Relation 埋め込まれたリレーションのフィールドを表示する

埋め込みリレーションがあり、ネストされたフィールドを表示する必要がある場合、Hydra データ プロバイダーの useEmbedded の値に応じて、記述する必要があるコードが異なります。

true (デフォルトの動作) の場合、ドット表記を使用してフィールドを表示する必要があります。

import {
  HydraAdmin,
  FieldGuesser,
  ListGuesser,
  ResourceGuesser
} from "@api-platform/admin";
import { TextField } from "react-admin";

const BooksList = (props) => (
  <ListGuesser {...props}>
    <FieldGuesser source="title" />
    {/* Use react-admin components directly when you want complex fields. */}
    <TextField label="Author first name" source="author.firstName" />
  </ListGuesser>
);

export default () => (
  <HydraAdmin entrypoint={process.env.REACT_APP_API_ENTRYPOINT}>
    <ResourceGuesser
      name="books"
      list={BooksList}
    />
  </HydraAdmin>
);

useEmbedded が明示的に false に設定されている場合は、リレーションを参照としてフェッチする必要があるかのようにコードを記述してください。

この場合、ドット セパレータを使用してこれを行うことはできません。

この動作では埋め込みデータを直接編集できないことに注意してください。 たとえば、API が次を返す場合:

{
  "@context": "/contexts/Book",
  "@id": "/books",
  "@type": "hydra:Collection",
  "hydra:member": [
    {
      "@id": "/books/07b90597-542e-480b-a6bf-5db223c761aa",
      "@type": "https://schema.org/Book",
      "title": "War and Peace",
      "author": {
        "@id": "/authors/d7a133c1-689f-4083-8cfc-afa6d867f37d",
        "@type": "https://schema.org/Author",
        "firstName": "Leo",
        "lastName": "Tolstoi"
      }
    }
  ],
  "hydra:totalItems": 1
}

リストにauthorを表示する場合は、次のコードを記述する必要があります。

import {
  HydraAdmin,
  FieldGuesser,
  ListGuesser,
  ResourceGuesser
} from "@api-platform/admin";
import { ReferenceField, TextField } from "react-admin";

const BooksList = (props) => (
  <ListGuesser {...props}>
    <FieldGuesser source="title" />
    {/* Use react-admin components directly when you want complex fields. */}
    <ReferenceField label="Author first name" source="author" reference="authors">
      <TextField source="firstName" />
    </ReferenceField>
  </ListGuesser>
);

export default () => (
  <HydraAdmin entrypoint={process.env.REACT_APP_API_ENTRYPOINT}>
    <ResourceGuesser
      name="books"
      list={BooksList}
    />
    <ResourceGuesser name="authors" />
  </HydraAdmin>
);

Using an Autocomplete Input for Relations リレーションにオートコンプリート入力を使用する

リレーションのフォーム入力にオートコンプリートのサポートを追加することで、API Platform Admin のカスタマイズ機能のおかげでさらに一歩進んでみましょう。

(book プロパティを介して) 多対 1 の関係でリンクされた Review リソースと Book リソースを公開する API を考えてみましょう。 この API は、次の PHP コードを使用します。

<?php
// api/src/Entity/Review.php
namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ApiResource]
class Review
{
    #[ORM\Id, ORM\Column, ORM\GeneratedValue]
    public ?int $id = null;

    #[ORM\ManyToOne]
    public Book $book;
}
<?php
// api/src/Entity/Book.php
namespace App\Entity;

use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ApiResource]
class Book
{
    #[ORM\Id, ORM\Column, ORM\GeneratedValue]
    public ?int $id = null;

    #[ORM\Column] 
    #[ApiFilter(SearchFilter::class, strategy: 'ipartial')]
    public string $title;

    #[ORM\OneToMany(targetEntity: Review::class, mappedBy: 'book')] 
    public $reviews;

    public function __construct()
    {
        $this->reviews = new ArrayCollection();
    }
}

Book リソース クラスの title プロパティの “部分検索” フィルターに注目してください。

それでは、リレーションセレクターのオートコンプリートを有効にするように API Platform Admin を構成しましょう。

import {
  HydraAdmin,
  ResourceGuesser,
  CreateGuesser,
  EditGuesser,
  InputGuesser
} from "@api-platform/admin";
import { ReferenceInput, AutocompleteInput } from "react-admin";

const ReviewsCreate = props => (
  <CreateGuesser {...props}>
    <InputGuesser source="author" />
    <ReferenceInput
      source="book"
      reference="books"
    >
      <AutocompleteInput
        filterToQuery={searchText => ({ title: searchText })}
        optionText="title"
        label="Books"
      />
    </ReferenceInput>

    <InputGuesser source="rating" />
    <InputGuesser source="body" />
    <InputGuesser source="publicationDate" />
  </CreateGuesser>
);

const ReviewsEdit = props => (
  <EditGuesser {...props}>
    <InputGuesser source="author" />

    <ReferenceInput
      source="book"
      reference="books"
    >
      <AutocompleteInput
        filterToQuery={searchText => ({ title: searchText })}
        optionText="title"
        label="Books"
      />
    </ReferenceInput>

    <InputGuesser source="rating" />
    <InputGuesser source="body" />
    <InputGuesser source="publicationDate" />
  </EditGuesser>
);

export default () => (
  <HydraAdmin entrypoint={process.env.REACT_APP_API_ENTRYPOINT}>
    <ResourceGuesser
      name="reviews"
      create={ReviewsCreate}
      edit={ReviewsEdit}
    />
  </HydraAdmin>
);


書籍がレビューに埋め込まれていて、useEmbedded パラメーターが true (デフォルトの動作) の場合、編集コンポーネントの ReferenceInput を変更する必要があります。

import {
  HydraAdmin,
  ResourceGuesser,
  CreateGuesser,
  EditGuesser,
  InputGuesser
} from "@api-platform/admin";
import { ReferenceInput, AutocompleteInput } from "react-admin";

const ReviewsCreate = props => (
  <CreateGuesser {...props}>
    <InputGuesser source="author" />
    <ReferenceInput
      source="book"
      reference="books"
    >
      <AutocompleteInput
        filterToQuery={searchText => ({ title: searchText })}
        optionText="title"
        label="Books"
      />
    </ReferenceInput>

    <InputGuesser source="rating" />
    <InputGuesser source="body" />
    <InputGuesser source="publicationDate" />
  </CreateGuesser>
);

const ReviewsEdit = props => (
  <EditGuesser {...props}>
    <InputGuesser source="author" />

    <ReferenceInput
      source="book"
      reference="books"
    >
      <AutocompleteInput
        filterToQuery={searchText => ({ title: searchText })}
        format={v => v['@id'] || v}
        optionText="title"
        label="Books"
      />
    </ReferenceInput>

    <InputGuesser source="rating" />
    <InputGuesser source="body" />
    <InputGuesser source="publicationDate" />
  </EditGuesser>
);

export default () => (
  <HydraAdmin entrypoint={process.env.REACT_APP_API_ENTRYPOINT}>
    <ResourceGuesser
      name="reviews"
      create={ReviewsCreate}
      edit={ReviewsEdit}
    />
  </HydraAdmin>
);

オートコンプリート フィールドが正しく機能するようになりました。