diff --git a/src/PSR7/Validators/BodyValidator/MultipartValidator.php b/src/PSR7/Validators/BodyValidator/MultipartValidator.php index 8342ccae..0c1621dc 100644 --- a/src/PSR7/Validators/BodyValidator/MultipartValidator.php +++ b/src/PSR7/Validators/BodyValidator/MultipartValidator.php @@ -29,7 +29,7 @@ use function array_diff_assoc; use function array_map; -use function array_replace; +use function array_replace_recursive; use function array_shift; use function explode; use function in_array; @@ -291,7 +291,24 @@ private function validateServerRequestMultipart( $files = $this->normalizeFiles($message->getUploadedFiles()); - $body = array_replace($body, $files); + // The PHP kernel separates file data (binary) from body data so it's + // imperative to combine the two arrays __recursively__ to have properly + // constructed arrays of objects (collections) prior to validating + // them against the schema + // + // Otherwise, the file array will overwrite the body array at the common + // __root__ element, which may result in an object validation error if + // the object also contains other properties (stored in the body array) + // + // Eg: Array of file objects with a text descriptor + // [ + // ['description' => , 'file' => ] + // ['description' => , 'file' => ] + // ['description' => , 'file' => ] + // ] + // + // The 'description' would be in $body and 'file' would be in $files + $body = array_replace_recursive($body, $files); $validator = new SchemaValidator($this->detectValidationStrategy($message)); try { diff --git a/tests/PSR7/Validators/BodyValidator/MultipartValidatorTest.php b/tests/PSR7/Validators/BodyValidator/MultipartValidatorTest.php index 2e2580e6..7f47e4a2 100644 --- a/tests/PSR7/Validators/BodyValidator/MultipartValidatorTest.php +++ b/tests/PSR7/Validators/BodyValidator/MultipartValidatorTest.php @@ -81,6 +81,27 @@ public function dataProviderMultipartGreen(): array [file content goes there] ------WebKitFormBoundaryWfPNVh4wuWBlyEyQ-- HTTP +, + ], + // multipart mixed message with nested file objects (collections) + [ + << new UploadedFile($imagePath, $imageSize, 0), ], ], + // multipart mixed message with nested file objects (collections) + // The PHP kernel separates binary data (files) from body data so it's + // important to test that this is correctly handled by the validator + // when dealing with nested objects + [ + 'post', + '/multipart/files/collections', + [ + 'files' => [ + ['caption' => 'Some caption'], + ['caption' => 'Some caption'], + ], + ], + [ + 'files' => [ + ['file' => new UploadedFile($imagePath, $imageSize, 0)], + ['file' => new UploadedFile($imagePath, $imageSize, 0)], + ], + ], + ], // Missing optional field with defined encoding [ 'post', diff --git a/tests/stubs/multipart.yaml b/tests/stubs/multipart.yaml index b018b4c6..3e8b0f57 100644 --- a/tests/stubs/multipart.yaml +++ b/tests/stubs/multipart.yaml @@ -54,6 +54,33 @@ paths: type: string format: binary + responses: + 204: + description: good post + /multipart/files/collections: + post: + summary: --- + operationId: post-multipart-file-collections + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + files: + type: array + items: + type: object + required: + - caption + - file + properties: + caption: + type: string + file: + type: string + format: binary + responses: 204: description: good post