API PlatformのOpenAPI生成で、エンティティのidをrequiredにする
API PlatformのOpenAPI生成で、nullableなエンティティプロパティの型をanyOfではなくoneOfで出力する の続きというか関連記事です。
背景
API Platform によって自動生成されたOpenAPIにおいて、Doctrineエンティティの id
プロパティはなぜか正規の手順では required
にできません。
正規の手順としては、公式ドキュメント を参考に
#[ApiProperty(required: true)]
private ?string $name = null;
とアトリビュートをつけるだけでプロパティを required
としてOpenAPIを生成してくれます。
アトリビュートではなくYAMLで設定する場合は以下のように書きます。
App\Entity\Foo: properties: name: required: true
しかし、Doctrineエンティティの id
プロパティだけは、なぜかこの方法を使っても required
として出力されてくれません。これは困ります。
具体的にAPI Platformの実装のどの部分が原因で、それが意図した仕様なのかバグなのかなど細かいことは何も調べられていません🙏
API Platformのバージョンは記事執筆時点で最新の安定版である 2.6.8 です。
API Platformのコードにおける原因箇所
API Platformのコードをgrepやvar_dumpを駆使して調べたところ、
- https://github.com/api-platform/core/blob/2.6/src/JsonSchema/SchemaFactory.php
- https://github.com/api-platform/core/blob/2.6/src/Hydra/JsonSchema/SchemaFactory.php
この2ファイルがJSONおよびJSON-LD(Hydra)それぞれのOpenAPIのスキーマを生成していることが分かりました。
API PlatformのOpenAPI生成で、nullableなエンティティプロパティの型をanyOfではなくoneOfで出力する
でも似たような対処をしましたが、今回もこの SchemaFactory
を デコレート して拡張した自作サービスに差し替えてあげることで対応できそうです。
なお、この辺り を見ると、JSON-LD用の SchemaFactory
が "jsonld"
以外のフォーマット向けに呼ばれたときは、JSON用 SchemaFactory
の結果をそのまま返すという実装になっているので、JSON-LDを有効にしているアプリではJSON-LD用の SchemaFactory
を差し替えてその戻り値を加工してあげればよさそうです。
SchemaFactory
を拡張する
API Platformの vendor/api-platform
配下を ApiPlatform\Core\Hydra\JsonSchema\SchemaFactory
でgrepするなり、Symfonyプラグインを入れたPhpStormなど補完が強力な環境で services.yaml
上で適当に apiplatformschemafactory
などとタイプしてみると、このクラスが api_platform.hydra.json_schema.schema_factory
というサービスIDでSymfonyにサービスとして登録されていることが分かります。
なので、Symfonyのサービスデコレート機能 を使って以下のようにサービスを差し替えてあげればよさそうです。
# config/services.yaml
services:
App\ApiPlatform\SchemaFactory: # というクラスを自作する
decorates: api_platform.hydra.json_schema.schema_factory
肝心の自作するクラスの内容ですが、
API PlatformのOpenAPI生成で、nullableなエンティティプロパティの型をanyOfではなくoneOfで出力する
と同様今回のクラスも final
クラスなので、継承して部分的に処理を変更することはできず、最終的な戻り値を加工してあげるしか方法はありません。
元の SchemaFactory
クラスの buildSchema()
メソッドの処理をvar_dumpなどしながら確認してみると、
-
ApiPlatform\Core\JsonSchema\Schema
クラスのインスタンスを返す -
Schema::getDefinitions()
でスキーマの\ArrayObject
が得られる - 各スキーマの
properties
カラムおよびrequired
カラムにプロパティの内容と必須かどうかの定義がある
ということが分かります。
というわけで、自作クラスの内容は以下のようになりました。
<?php
declare(strict_types=1);
namespace App\ApiPlatform;
use ApiPlatform\Core\JsonSchema\Schema;
use ApiPlatform\Core\JsonSchema\SchemaFactoryInterface;
/**
* @see \ApiPlatform\Core\Hydra\JsonSchema\SchemaFactory
*/
final class SchemaFactory implements SchemaFactoryInterface
{
public function __construct(private SchemaFactoryInterface $decorated)
{
}
public function buildSchema(string $className, string $format = 'json', string $type = Schema::TYPE_OUTPUT, ?string $operationType = null, ?string $operationName = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema
{
$schema = $this->decorated->buildSchema($className, $format, $type, $operationType, $operationName, $schema, $serializerContext, $forceCollection);
$definitions = $schema->getDefinitions();
if ($key = $schema->getRootDefinitionKey()) {
if (isset($definitions[$key]['properties']['id']) && !in_array('id', $definitions[$key]['required'] ?? [], true)) {
$definitions[$key]['required'][] = 'id';
}
}
return $schema;
}
}
結果
上記のとおり自作クラスを書いてサービスを decorates
によって差し替えた結果、API Platformが生成するOpenAPIの内容は
{
"components": {
"schemas": {
"Foo": {
"type": "object",
"required": [
"既存の必須プロパティ",
:
:
+ "id"
],
"properties": {
:
:
}
},
},
},
}
という感じで期待どおり id
プロパティを required
に追加することができました。
SwaggerUI上でも、
こんな感じでちゃんと必須になっています。(余計なプロパティはdevtoolで消してあります)
めでたしめでたし🍵
Discussion