`json_stream`: objects inside a list stay uninitialized lazy ghosts, so `#[Assert\Valid]` validates them as blank
Les mainteneurs répondent en général sous 2 jours
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Accessibilité débutants
- 48/100
- Type d'issue
- Bug
- Clarté
- Clairement spécifiée
- Activité
- Active
- Domaine
- backend, backend-api-design
Piste de recherche
Start in JsonStreamerProvider and trace how streamed objects in list properties reach ValidateProvider; compare this path with the non-streaming Serializer path. Check the relevant provider and validation tests, then add a regression case for a DTO list with #[Assert\Valid] and #[Assert\NotBlank]. Done means valid list items pass validation and invalid ones still produce violations.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
API Platform version(s) affected: 5.0.5 (api-platform/symfony, api-platform/serializer); the same code is on main
Environment: PHP 8.5.4, symfony/json-streamer 8.1.10, symfony/validator 8.1.11
Description
When an operation uses jsonStream: true, ApiPlatform\Serializer\State\JsonStreamerProvider reads the request body with StreamReaderInterface::read($request->getContent(true), …). Because the input is a stream, JsonStreamer builds the objects through its LazyInstantiator (PHP lazy ghosts). The provider then initializes only the top-level object:
if (\PHP_VERSION_ID > 80400) {
$refl = new \ReflectionClass($data);
if ($refl->isUninitializedLazyObject($data)) {
$refl->initializeLazyObject($data);
}
}
Objects inside a list<…> property are still uninitialized ghosts when ValidateProvider runs. The Validator reads properties through PropertyMetadata::getPropertyValue(), which returns null when ReflectionProperty::isInitialized() is false. On an uninitialized ghost, isInitialized() returns false and does not trigger initialization. So every constraint on a list item sees an empty value, and a valid payload is rejected with 422.
A single nested object property (public ?Line $line) is not affected in my tests: only objects that are list items are.
How to reproduce
<?php
namespace App\ApiResource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use Symfony\Component\Validator\Constraints as Assert;
#[ApiResource(operations: [new Post(uriTemplate: '/repro/orders', processor: [self::class, 'echo'])], jsonStream: true)]
final class ReproOrder
{
/** @var list<ReproLine> */
#[Assert\Valid]
public array $lines = [];
public static function echo(self $data): self
{
return $data;
}
}
final class ReproLine
{
#[Assert\NotBlank]
public string $label = '';
}
with formats: { json: ['application/json'] } in api_platform.yaml, then:
curl -X POST http://localhost/api/repro/orders \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d '{"lines":[{"label":"A"},{"label":"B"}]}'
Expected: 201 {"lines":[{"label":"A"},{"label":"B"}]}
Actual:
422 {"status":422,"violations":[
{"propertyPath":"lines[0].label","message":"This value should not be blank.", …},
{"propertyPath":"lines[1].label","message":"This value should not be blank.", …}
], …}
The same request without jsonStream: true (Serializer path) returns 201.
Possible Solution
Initialize the whole object graph in JsonStreamerProvider after reading, not just the root. Here is a userland workaround that fixes it: a decorator placed between the JSON streamer provider (priority 310) and deserialize/validate (300/200):
#[AsDecorator('api_platform.state_provider.main', priority: 305)]
final readonly class InitializeStreamedObjects implements ProviderInterface
{
public function __construct(#[AutowireDecorated] private ProviderInterface $inner)
{
}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
$data = $this->inner->provide($operation, $uriVariables, $context);
if (($context['request'] ?? null)?->attributes->get('deserialized')) {
self::initialize($data);
}
return $data;
}
private static function initialize(mixed $value): void
{
if (is_array($value)) {
array_walk($value, static fn (mixed $item) => self::initialize($item));
return;
}
if (!is_object($value)) {
return;
}
$class = new \ReflectionClass($value);
$class->initializeLazyObject($value);
array_map(static fn (\ReflectionProperty $property) => self::initialize($property->isInitialized($value) ? $property->getValue($value) : null), $class->getProperties(\ReflectionProperty::IS_PUBLIC));
}
}
Alternatively, the Validator could treat uninitialized lazy objects specially, though that seems a wider change than fixing it where API Platform hands the object over.
Additional Context
Found while moving an app's write endpoints to json_stream: true with nested DTO lists (a check-list editor PUTting lists of items).
- Langage dominant
- PHP
- Étoiles
- 2.6k
- Forks
- 988
- Merge moyen
- 4 j 10 h
- PR mergées (30 j)
- 109
Préparer son environnement
- Aucun Dockerfile ni fichier Docker Compose
- Propose un modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de api-platform/core
-
API Platform ignores array shapes in the JSON SchemaPeut-être pris @afraca l’a pris il y a 13 jours. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
api-platform/core#8766 ·
Les mainteneurs répondent en général sous 2 jours
-
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
api-platform/core#8744 ·
Les mainteneurs répondent en général sous 2 jours
-
JSON:API: resource schemas (`*.jsonapi`) have `properties` but no `type: object`Peut-être pris Une pull request liée à cette issue est ouverte ou déjà fusionnée. Ouverte
Difficulté 2/5 Moins d'une heure Accessibilité débutants 80/100
api-platform/core#8701 ·
Les mainteneurs répondent en général sous 2 jours
-
Difficulté 4/5 3-5 jours Accessibilité débutants 55/100
api-platform/core#8816 ·
Les mainteneurs répondent en général sous 2 jours
-
Difficulté 3/5 1-2 jours Accessibilité débutants 66/100
api-platform/core#8815 · 1 commentaire ·
Les mainteneurs répondent en général sous 2 jours
Toutes les issues de api-platform/core
Issues similaires
-
Difficulté 2/5 1-3 heures Accessibilité débutants 74/100
Les mainteneurs répondent en général sous 1 jour
-
docs good first issue php
Difficulté 1/5 1-3 heures Accessibilité débutants 90/100
Les mainteneurs répondent en général sous 1 jour
-
Area: UI Priority: High Status: Available Type: Maintenance
Difficulté 2/5 1-3 heures Accessibilité débutants 62/100
Les mainteneurs répondent en général sous 1 jour
-
fix(flexi-content): wrap editor aria-labels with i18nPeut-être pris @Ankitavasudev l’a pris aujourd’hui. Ouvertefirst-timers-only focus: accessibility focus: blocks focus: i18n good first issue hacktoberfest help wanted type: bug up-for-grabs winter-of-code
Difficulté 1/5 1-3 heures Accessibilité débutants 90/100
noruzzamans/gslider-blocks#697 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100