UploadedFileCreator::createFromArray()   A
last analyzed

Complexity

Conditions 4
Paths 2

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 4
eloc 10
c 1
b 0
f 1
nc 2
nop 1
dl 0
loc 15
ccs 12
cts 12
cp 1
crap 4
rs 9.9332
1
<?php
2
3
declare(strict_types=1);
4
5
namespace HttpSoft\ServerRequest;
6
7
use HttpSoft\Message\UploadedFile;
8
use InvalidArgumentException;
9
use Psr\Http\Message\StreamInterface;
10
use Psr\Http\Message\UploadedFileInterface;
11
12
use function is_array;
13
use function sprintf;
14
15
final class UploadedFileCreator
16
{
17
    /**
18
     * Creates an instance of `Psr\Http\Message\UploadedFileInterface`.
19
     *
20
     * @param StreamInterface|string|resource $streamOrFile
21
     * @param int $size
22
     * @param int $error
23
     * @param string|null $clientFilename
24
     * @param string|null $clientMediaType
25
     * @return UploadedFileInterface
26
     */
27 2
    public static function create(
28
        $streamOrFile,
29
        int $size,
30
        int $error,
31
        ?string $clientFilename = null,
32
        ?string $clientMediaType = null
33
    ): UploadedFileInterface {
34 2
        return new UploadedFile($streamOrFile, $size, $error, $clientFilename, $clientMediaType);
35
    }
36
37
    /**
38
     * Creates an instance of `Psr\Http\Message\UploadedFileInterface` from an one-dimensional array `$file`.
39
     * The array structure must be the same as item in the global `$_FILES` array.
40
     *
41
     * Example of array structure format:
42
     *
43
     * ```php
44
     * $file = [
45
     *     'name' => 'filename.jpg', // optional
46
     *     'type' => 'image/jpeg', // optional
47
     *     'tmp_name' => '/tmp/php/php6hst32',
48
     *     'error' => 0, // UPLOAD_ERR_OK
49
     *     'size' => 98174,
50
     * ];
51
     * ```
52
     *
53
     * @see https://www.php.net/manual/features.file-upload.post-method.php
54
     * @see https://www.php.net/manual/reserved.variables.files.php
55
     *
56
     * @param array $file
57
     * @return UploadedFileInterface
58
     * @throws InvalidArgumentException
59
     * @psalm-suppress MixedArgument
60
     */
61 11
    public static function createFromArray(array $file): UploadedFileInterface
62
    {
63 11
        if (!isset($file['tmp_name']) || !isset($file['size']) || !isset($file['error'])) {
64 3
            throw new InvalidArgumentException(sprintf(
65 3
                'Invalid array `$file` to `%s`. One of the items is missing: "tmp_name" or "size" or "error".',
66 3
                __METHOD__
67 3
            ));
68
        }
69
70 8
        return new UploadedFile(
71 8
            $file['tmp_name'],
72 8
            $file['size'],
73 8
            $file['error'],
74 8
            $file['name'] ?? null,
75 8
            $file['type'] ?? null
76 8
        );
77
    }
78
79
    /**
80
     * Converts each value of the multidimensional array `$files`
81
     * to an `Psr\Http\Message\UploadedFileInterface` instance.
82
     *
83
     * The method uses recursion, so the `$files` array can be of any nesting type.
84
     * The array structure must be the same as the global `$_FILES` array.
85
     * All key names in the `$files` array will be saved.
86
     *
87
     * @see https://www.php.net/manual/features.file-upload.post-method.php
88
     * @see https://www.php.net/manual/reserved.variables.files.php
89
     *
90
     * @param array $files
91
     * @return UploadedFileInterface[]|array[]
92
     * @throws InvalidArgumentException
93
     * @psalm-suppress MixedAssignment
94
     */
95 27
    public static function createFromGlobals(array $files = []): array
96
    {
97 27
        $uploadedFiles = [];
98
99 27
        foreach ($files as $key => $file) {
100 24
            if ($file instanceof UploadedFileInterface) {
101 1
                $uploadedFiles[$key] = $file;
102 1
                continue;
103
            }
104
105 23
            if (!is_array($file)) {
106 12
                throw new InvalidArgumentException(sprintf(
107 12
                    'Error in the `%s`. Invalid file specification for normalize in array `$files`.',
108 12
                    __METHOD__
109 12
                ));
110
            }
111
112 14
            if (!isset($file['tmp_name'])) {
113 4
                $uploadedFiles[$key] = self::createFromGlobals($file);
114 1
                continue;
115
            }
116
117 11
            if (is_array($file['tmp_name'])) {
118 5
                $uploadedFiles[$key] = self::createMultipleUploadedFiles($file);
119 3
                continue;
120
            }
121
122 6
            $uploadedFiles[$key] = self::createFromArray($file);
123
        }
124
125 11
        return $uploadedFiles;
126
    }
127
128
    /**
129
     * Creates an array instances of `Psr\Http\Message\UploadedFileInterface` from an multidimensional array `$files`.
130
     * The array structure must be the same as multidimensional item in the global `$_FILES` array.
131
     *
132
     * @param array $files
133
     * @return UploadedFileInterface[]
134
     * @throws InvalidArgumentException
135
     * @psalm-suppress MixedArgument
136
     * @psalm-suppress MixedArgumentTypeCoercion
137
     */
138 5
    private static function createMultipleUploadedFiles(array $files): array
139
    {
140
        if (
141 5
            !isset($files['tmp_name']) || !is_array($files['tmp_name'])
142 5
            || !isset($files['size']) || !is_array($files['size'])
143 5
            || !isset($files['error']) || !is_array($files['error'])
144
        ) {
145 2
            throw new InvalidArgumentException(sprintf(
146 2
                'Invalid array `$files` to `%s`. One of the items is missing or is not an array:'
147 2
                . ' "tmp_name" or "size" or "error".',
148 2
                __METHOD__
149 2
            ));
150
        }
151
152 3
        return self::buildTree(
153 3
            $files['tmp_name'],
154 3
            $files['size'],
155 3
            $files['error'],
156 3
            $files['name'] ?? null,
157 3
            $files['type'] ?? null,
158 3
        );
159
    }
160
161
    /**
162
     * Building a normalized tree with the correct nested structure
163
     * and `Psr\Http\Message\UploadedFileInterface` instances.
164
     *
165
     * @param string[]|array[] $tmpNames
166
     * @param int[]|array[] $sizes
167
     * @param int[]|array[] $errors
168
     * @param string[]|array[]|null $names
169
     * @param string[]|array[]|null $types
170
     * @return UploadedFileInterface[]
171
     * @psalm-suppress InvalidReturnType
172
     * @psalm-suppress InvalidReturnStatement
173
     * @psalm-suppress MixedArgumentTypeCoercion
174
     */
175 3
    private static function buildTree(array $tmpNames, array $sizes, array $errors, ?array $names, ?array $types): array
176
    {
177 3
        $tree = [];
178
179 3
        foreach ($tmpNames as $key => $value) {
180 3
            if (is_array($value)) {
181 1
                $tree[$key] = self::buildTree(
182 1
                    $tmpNames[$key],
183 1
                    $sizes[$key],
184 1
                    $errors[$key],
185 1
                    $names[$key] ?? null,
186 1
                    $types[$key] ?? null,
187 1
                );
188
            } else {
189 3
                $tree[$key] = self::createFromArray([
190 3
                    'tmp_name' => $tmpNames[$key],
191 3
                    'size' => $sizes[$key],
192 3
                    'error' => $errors[$key],
193 3
                    'name' => $names[$key] ?? null,
194 3
                    'type' => $types[$key] ?? null,
195 3
                ]);
196
            }
197
        }
198
199 3
        return $tree;
200
    }
201
}
202