`json_stream`: objects inside a list stay uninitialized lazy ghosts, so `#[Assert\Valid]` validates them as blank
Maintainer thường phản hồi trong vòng 2 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 48/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Lĩnh vực
- backend, backend-api-design
Hướng nghiên cứu
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.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
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).
- Ngôn ngữ chính
- PHP
- Star
- 2.6k
- Fork
- 988
- Merge trung bình
- 4 ngày 10 giờ
- Pull request đã merge (30 ngày)
- 114
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của api-platform/core
-
API Platform ignores array shapes in the JSON SchemaCó thể đã có người làm @afraca đã nhận 11 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
api-platform/core#8766 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
api-platform/core#8744 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
JSON:API: resource schemas (`*.jsonapi`) have `properties` but no `type: object`Có thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 2/5 Dưới một giờ Mức phù hợp với người mới 80/100
api-platform/core#8701 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 55/100
api-platform/core#8816 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 66/100
api-platform/core#8815 ·
Maintainer thường phản hồi trong vòng 2 ngày
Tất cả issue của api-platform/core
Issue tương tự
-
Visual COBOL: missing 11.0 and 12.0, and 9.0 date differs from Rocket Software's lifecycle pageĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
endoflife-date/endoflife.date#11561 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
good first issue priority:low type:bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
crazy-goat/fdb-php#184 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
nextcloud/groupfolders#5218 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
nextcloud/sharelisting#624 ·
-
`wp theme mod set` prints its failure message as `Success` and exits 0Có thể đã có người làm @dhrupo đã nhận hôm nay. Đang mởbug command:theme-mod-set status:unconfirmed
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
wp-cli/extension-command#570 ·