*/ final class UploadedFileNormalizer { private function __construct() { } /** * Return an UploadedFile instance array. * * @param array $files An array which respect $_FILES structure * * @return UploadedFileTree * * @throws InvalidArgumentException for unrecognized values */ public static function normalize(array $files): array { $normalized = []; foreach ($files as $key => $value) { if ($value instanceof UploadedFileInterface) { $normalized[$key] = $value; } elseif (is_array($value) && array_key_exists('tmp_name', $value)) { $normalized[$key] = self::createUploadedFileFromSpec($value); } elseif (is_array($value)) { $normalized[$key] = self::normalize($value); continue; } else { throw new InvalidArgumentException('Invalid value in files specification'); } } return $normalized; } /** * Create and return an UploadedFile instance from a $_FILES specification. * * If the specification represents an array of values, this method will * delegate to normalizeNestedFileSpec() and return that return value. * * @param array $value $_FILES struct * * @return UploadedFileInterface|UploadedFileTree */ private static function createUploadedFileFromSpec(array $value) { self::assertFileSpec($value); if (is_array($value['tmp_name'])) { return self::normalizeNestedFileSpec($value); } return new UploadedFile( $value['tmp_name'], Integers::assertNonNegativeInteger($value['size'], 'Uploaded file size'), Integers::assertNonNegativeInteger($value['error'], 'Uploaded file error'), $value['name'] ?? null, $value['type'] ?? null ); } private static function assertFileSpec(array $value): void { if (!isset($value['tmp_name'], $value['size'], $value['error'])) { throw new InvalidArgumentException('Invalid file specification; expected keys "tmp_name", "size", and "error".'); } } /** * Normalize an array of file specifications. * * Loops through all nested files and returns a normalized array of * UploadedFileInterface instances. * * @return UploadedFileTree */ private static function normalizeNestedFileSpec(array $files = []): array { self::assertNestedFileSpec($files); $normalizedFiles = []; foreach (array_keys($files['tmp_name']) as $key) { if (!array_key_exists($key, $files['size']) || !array_key_exists($key, $files['error'])) { throw new InvalidArgumentException('Invalid nested file specification; expected "tmp_name", "size", and "error" arrays to have matching keys.'); } $spec = [ 'tmp_name' => $files['tmp_name'][$key], 'size' => $files['size'][$key], 'error' => $files['error'][$key], 'name' => $files['name'][$key] ?? null, 'type' => $files['type'][$key] ?? null, ]; $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); } return $normalizedFiles; } private static function assertNestedFileSpec(array $files): void { foreach (['tmp_name', 'size', 'error'] as $key) { if (!isset($files[$key]) || !is_array($files[$key])) { throw new InvalidArgumentException('Invalid nested file specification; expected keys "tmp_name", "size", and "error" to be arrays.'); } } foreach (['name', 'type'] as $key) { if (isset($files[$key]) && !is_array($files[$key])) { throw new InvalidArgumentException(sprintf('Invalid nested file specification; expected key "%s" to be an array when present.', $key)); } } } }