`json_stream`: objects inside a list stay uninitialized lazy ghosts, so `#[Assert\Valid]` validates them as blank
I maintainer di solito rispondono entro 2 giorni
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 48/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Ambito
- backend, backend-api-design
Direzione di ricerca
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.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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).
- Lingua principale
- PHP
- Stelle
- 2.6k
- Fork
- 988
- Merge medio
- 4g 10h
- PR unite (30g)
- 114
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di api-platform/core
-
API Platform ignores array shapes in the JSON SchemaForse già presa @afraca l’ha presa 11 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
api-platform/core#8766 ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
api-platform/core#8744 ·
I maintainer di solito rispondono entro 2 giorni
-
JSON:API: resource schemas (`*.jsonapi`) have `properties` but no `type: object`Forse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 2/5 Meno di un'ora Idoneità per principianti 80/100
api-platform/core#8701 ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 55/100
api-platform/core#8816 ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 66/100
api-platform/core#8815 ·
I maintainer di solito rispondono entro 2 giorni
Tutte le issue di api-platform/core
Issue simili
-
Visual COBOL: missing 11.0 and 12.0, and 9.0 date differs from Rocket Software's lifecycle pageAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
endoflife-date/endoflife.date#11561 ·
I maintainer di solito rispondono entro 1 giorno
-
good first issue priority:low type:bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
crazy-goat/fdb-php#184 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
nextcloud/groupfolders#5218 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
nextcloud/sharelisting#624 ·
-
`wp theme mod set` prints its failure message as `Success` and exits 0Forse già presa @dhrupo l’ha presa oggi. Apertabug command:theme-mod-set status:unconfirmed
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
wp-cli/extension-command#570 ·