Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

[Elasticsearch] Documented snake_case → camelCase document field conversion never happens

オープン
#8,470 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
55/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
elasticsearch, php, symfony
領域
backend, search

調査の方向性

src/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php、src/Symfony/Bundle/Resources/config/elasticsearch.php、src/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php から始めて、どの name converter が注入されるのかを追跡します。snake_case の Elasticsearch フィールドで再現し、tests/Functional/Elasticsearch/ReadTest.php と tests/Fixtures/Elasticsearch/Fixtures/user.json を比較します。完了の条件は、文書化された snake_case から camelCase への動作がカバーされて機能すること、または矛盾するドキュメントあるいはデフォルト値が削除されることです。

索引モデルが issue の本文から書いたものです。

説明

API Platform version(s) affected: 4.3.17 (4.x since MetadataAwareNameConverterPass was introduced)

Description

The Elasticsearch documentation states:

API Platform will automatically disable write operations and snake_case document fields will automatically be converted to camelCase object properties during serialization.

This conversion never happens. A content_key document field does not populate a $contentKey property — the property keeps its default value, silently.

InnerFieldsNameConverter does implement the documented behaviour, as its constructor default:

// src/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php
public function __construct(private readonly NameConverterInterface $inner = new CamelCaseToSnakeCaseNameConverter())

but that default is unreachable. The service definition passes an argument that is always valid:

// src/Symfony/Bundle/Resources/config/elasticsearch.php
$services->set('api_platform.elasticsearch.name_converter.inner_fields', InnerFieldsNameConverter::class)
    ->args([service('api_platform.name_converter')->ignoreOnInvalid()]);

ignoreOnInvalid() would drop the argument — and let the CamelCaseToSnakeCaseNameConverter default apply — only if api_platform.name_converter did not exist. But MetadataAwareNameConverterPass defines and aliases it unconditionally, whether or not the user configured api_platform.name_converter:

// src/Symfony/Bundle/DependencyInjection/Compiler/MetadataAwareNameConverterPass.php
$container->setDefinition('api_platform.name_converter.metadata_aware', $definition);
$container->setAlias('api_platform.name_converter', 'api_platform.name_converter.metadata_aware');

Its only early return is !$container->hasDefinition('serializer.mapping.class_metadata_factory'), which never holds in a Symfony app with the serializer enabled.

Documents are therefore denormalized through MetadataAwareNameConverter, which is an identity converter for every property that has no #[SerializedName].

How to reproduce

Index content holds:

{ "_id": "abc", "_source": { "content_key": "my-key" } }

Resource:

#[ApiResource(stateOptions: new Options(index: 'content'))]
class Content
{
    public string $id = '';
    public string $contentKey = '';
}

GET /contents/abc returns "contentKey": "". Renaming the property to $content_key returns "my-key", confirming the document key is matched literally.

Reproduced directly against the service, without HTTP:

$hit = ['_id' => 'abc', '_source' => ['content_key' => 'my-key']];
$content = $container->get('api_platform.elasticsearch.normalizer.document')
    ->denormalize($hit, Content::class, DocumentNormalizer::FORMAT);

$content->contentKey; // '' — expected 'my-key'

api_platform.elasticsearch.name_converter.inner_fields receives HydraPrefixNameConverter → MetadataAwareNameConverter, and denormalize('content_key', Content::class, 'elasticsearch') returns 'content_key'.

Second trap: #[SerializedName] does not work around it

The obvious workaround fails when the property carries serialization groups, which is the common case:

#[Groups(['content:read'])]
#[SerializedName('content_key')]
public string $contentKey = '';

MetadataAwareNameConverter::getCacheValueForAttributesMetadata() skips any attribute whose metadata has groups when the context has none — and the Elasticsearch denormalization context carries no groups:

if ($metadataGroups && !array_intersect($metadataGroups, $contextGroups) && !\in_array('*', $contextGroups, true)) {
    continue;
}

Normalization has no such group check, so the result is the worst of both: the JSON output field is renamed to content_key, while reading the document still fails.

Regarding #4053

#4053 reports this exact symptom and was closed as fixed, referencing tests/Functional/Elasticsearch/ReadTest.php. That test cannot catch it: the fixtures store camelCase document fields —

// tests/Fixtures/Elasticsearch/Fixtures/user.json
{ "firstName": "Kilian", "lastName": "Jornet", "registeredAt": "2009-09-01" }

— so it covers multi-word camelCase fields, not snake_case documents. Worth reopening, or covering with a fixture using snake_case keys.

Possible Solution

Either behaviour or documentation, depending on the intended convention:

  1. Restore the documented behaviour — inject serializer.name_converter.camel_case_to_snake_case into api_platform.elasticsearch.name_converter.inner_fields unless the user explicitly configured api_platform.name_converter (the compiler pass knows the difference: $container->hasAlias(...) before it sets its own alias), or expose a dedicated api_platform.elasticsearch.name_converter setting.
  2. Drop the claim — the same doc page also says "all fields should be lower case and should use camelCase for combining words", which contradicts the snake_case sentence and matches the fixtures. If camelCase documents are the intended convention, remove the sentence and the misleading InnerFieldsNameConverter constructor default.

Either way the current state is a silent data loss: a mismatched field yields an empty property, with no error.

Additional Context

Userland workaround, scoped to Elasticsearch so the API output stays camelCase:

services:
    api_platform.elasticsearch.name_converter.inner_fields:
        class: ApiPlatform\Elasticsearch\Serializer\NameConverter\InnerFieldsNameConverter
        autowire: false
        autoconfigure: false
        arguments: ['@serializer.name_converter.camel_case_to_snake_case']

PHP 8.4, Symfony 8.1, symfony/serializer 8.1.4, api-platform/elasticsearch 4.3.17, Elasticsearch 8.

主要言語
PHP
スター
2.6k
フォーク
982
平均マージ
1日 16時間
マージ済み PR(30日)
59

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

api-platform/core のほかの issue

api-platform/core の issue をすべて見る

似ている issue

PHP の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。