['value' => 1 ] ], [], new NullLogger()); expect($input)->toBe(['element' => ['value' => 1]]); }); readonly class MappedElement { public function __construct(#[FromInput('name')] public int $value) {} } readonly class MappedNode { public function __construct(public MappedElement $element) {} } test('basic nested input map', function () { $input = DataObjectFactory::mapInput(MappedNode::class, ['element' => ['name' => 1 ] ], [], new NullLogger()); expect($input)->toBe(['element' => ['value' => 1]]); }); readonly class MappedCollectionItem { public function __construct(#[FromInput('id_item')] public int $idItem) {} } readonly class MappedCollectionRoot { /** * @param Collection $items */ public function __construct(public string $text, #[FromInput('data')] public Collection $items) {} } test('using from input nested', function () { $mapped = DataObjectFactory::mapInput(MappedCollectionRoot::class, [ 'text' => 'abc', 'data' => [ [ 'id_item' => 1 ], [ 'id_item' => 2 ], [ 'id_item' => 4 ], [ 'id_item' => 8 ], ], ], [], new NullLogger()); expect($mapped)->toBe([ 'text' => 'abc', 'items' => [ [ 'idItem' => 1 ], [ 'idItem' => 2 ], [ 'idItem' => 4 ], [ 'idItem' => 8 ], ], ]); }); readonly class CollectionRoot { /** * @param Collection $items */ public function __construct(public string $text, public Collection $items) {} } test('using from input', function () { $mapped = DataObjectFactory::mapInput(MappedCollectionRoot::class, [ 'text' => 'abc', 'items' => [ [ 'id_item' => 1 ], [ 'id_item' => 2 ], [ 'id_item' => 4 ], [ 'id_item' => 8 ], ], ], [], new NullLogger()); expect($mapped)->toBe([ 'text' => 'abc', 'items' => [ [ 'idItem' => 1 ], [ 'idItem' => 2 ], [ 'idItem' => 4 ], [ 'idItem' => 8 ], ], ]); }); readonly class AnnotatedArrayItem { public function __construct(#[FromInput('name')] public float $value) {} } readonly class AnnotatedArray { /** * @param array $items */ public function __construct(public array $items) {} } test('annotated array', function () { $mapped = DataObjectFactory::mapInput( AnnotatedArray::class, ['items' => [['name' => 1], ['name' => 2]]], [], new NullLogger(), ); expect($mapped)->toBe(['items' => [['value' => 1], ['value' => 2]]]); }); test('route parameter priority over from input', function () { $dto = new class (123) { public function __construct( #[FromRouteParameter('user_id')] #[FromInput('user_id')] public int $id, ) {} }; $result = DataObjectFactory::mapInput( $dto::class, ['user_id' => 456], ['user_id' => 123], new \Psr\Log\NullLogger(), ); expect($result['id'])->toBe(123); }); test('multiple route parameters', function () { $dto = new class (45, 89, 'A') { public function __construct( #[FromRouteParameter('course_id')] public int $courseId, #[FromRouteParameter('student_id')] public int $studentId, #[FromInput('grade')] public string $grade, ) {} }; $result = DataObjectFactory::mapInput( $dto::class, ['grade' => 'A'], ['course_id' => 45, 'student_id' => 89], new \Psr\Log\NullLogger(), ); expect($result['courseId'])->toBe(45) ->and($result['studentId'])->toBe(89) ->and($result['grade'])->toBe('A'); }); test('route parameter with nested object', function () { $addressDto = new class ('Main St') { public function __construct(public string $street) {} }; $dto = new class (123, $addressDto) { public function __construct( #[FromRouteParameter('user_id')] public int $userId, public $address, ) {} }; $result = DataObjectFactory::mapInput( $dto::class, ['user_id' => 456, 'address' => ['street' => 'Main St']], ['user_id' => 123], new \Psr\Log\NullLogger(), ); expect($result['userId'])->toBe(123) ->and($result['address']['street'])->toBe('Main St'); });