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>
);