Passed
Push — master ( 9b6318...46ddd5 )
by Evgeniy
01:25
created

UploadedFileCreator::createMultipleUploadedFiles()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7.2269

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 7
eloc 14
nc 2
nop 1
dl 0
loc 20
ccs 10
cts 12
cp 0.8333
crap 7.2269
rs 8.8333
c 1
b 0
f 1
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 1
    public static function create(
28
        $streamOrFile,
29
        int $size,
30
        int $error,
31
        string $clientFilename = null,
32
        string $clientMediaType = null
33
    ): UploadedFileInterface {
34 1
        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
     */
60 10
    public static function createFromArray(array $file): UploadedFileInterface
61
    {
62 10
        if (!isset($file['tmp_name']) || !isset($file['size']) || !isset($file['error'])) {
63 3
            throw new InvalidArgumentException(sprintf(
64
                'Invalid array `$file` to `%s`. One of the items is missing: "tmp_name" or "size" or "error".',
65 3
                __METHOD__
66
            ));
67
        }
68
69 7
        return new UploadedFile(
70 7
            $file['tmp_name'],
71 7
            $file['size'],
72 7
            $file['error'],
73 7
            $file['name'] ?? null,
74 7
            $file['type'] ?? null
75
        );
76
    }
77
78
    /**
79
     * Converts each value of the multidimensional array `$files`
80
     * to an `Psr\Http\Message\UploadedFileInterface` instance.
81
     *
82
     * The method uses recursion, so the `$files` array can be of any nesting type.
83
     * The array structure must be the same as the global `$_FILES` array.
84
     * All key names in the `$files` array will be saved.
85
     *
86
     * @see https://www.php.net/manual/features.file-upload.post-method.php
87
     * @see https://www.php.net/manual/reserved.variables.files.php
88
     *
89
     * @param array $files
90
     * @return UploadedFileInterface[]|array[][]
91
     * @throws InvalidArgumentException
92
     */
93 22
    public static function createFromGlobals(array $files = []): array
94
    {
95 22
        $uploadedFiles = [];
96
97 22
        foreach ($files as $key => $file) {
98 19
            if ($file instanceof UploadedFileInterface) {
99
                $uploadedFiles[$key] = $file;
100
                continue;
101
            }
102
103 19
            if (!is_array($file)) {
104 11
                throw new InvalidArgumentException(sprintf(
105
                    'Error in the `%s`. Invalid file specification for normalize in array `$files`.',
106 11
                    __METHOD__
107
                ));
108
            }
109
110 10
            if (!isset($file['tmp_name'])) {
111 2
                $uploadedFiles[$key] = self::createFromGlobals($file);
112
                continue;
113
            }
114
115 8
            if (is_array($file['tmp_name'])) {
116 3
                $uploadedFiles[$key] = self::createMultipleUploadedFiles($file);
117 3
                continue;
118
            }
119
120 5
            $uploadedFiles[$key] = self::createFromArray($file);
121
        }
122
123 9
        return $uploadedFiles;
124
    }
125
126
    /**
127
     * Creates an array instances of `Psr\Http\Message\UploadedFileInterface` from an multidimensional array `$files`.
128
     * The array structure must be the same as multidimensional item in the global `$_FILES` array.
129
     *
130
     * @param array $files
131
     * @return UploadedFileInterface[]
132
     * @throws InvalidArgumentException
133
     */
134 3
    private static function createMultipleUploadedFiles(array $files): array
135
    {
136
        if (
137 3
            !isset($files['tmp_name']) || !is_array($files['tmp_name'])
138 3
            || !isset($files['size']) || !is_array($files['size'])
139 3
            || !isset($files['error']) || !is_array($files['error'])
140
        ) {
141
            throw new InvalidArgumentException(sprintf(
142
                'Invalid array `$files` to `%s`. One of the items is missing or is not an array:'
143
                . ' "tmp_name" or "size" or "error".',
144
                __METHOD__
145
            ));
146
        }
147
148 3
        return self::buildTree(
149 3
            $files['tmp_name'],
150 3
            $files['size'],
151 3
            $files['error'],
152 3
            $files['name'] ?? null,
153 3
            $files['type'] ?? null,
154
        );
155
    }
156
157
    /**
158
     * Building a normalized tree with the correct nested structure
159
     * and `Psr\Http\Message\UploadedFileInterface` instances.
160
     *
161
     * @param string[]|array[] $tmpNames
162
     * @param int[]|array[] $sizes
163
     * @param int[]|array[] $errors
164
     * @param string[]|array[]|null $names
165
     * @param string[]|array[]|null $types
166
     * @return UploadedFileInterface[]|array[]
167
     */
168 3
    private static function buildTree(array $tmpNames, array $sizes, array $errors, ?array $names, ?array $types): array
169
    {
170 3
        $tree = [];
171
172 3
        foreach ($tmpNames as $key => $value) {
173 3
            if (is_array($value)) {
174 1
                $tree[$key] = self::buildTree(
175 1
                    $tmpNames[$key],
176 1
                    $sizes[$key],
177 1
                    $errors[$key],
178 1
                    $names[$key] ?? null,
179 1
                    $types[$key] ?? null,
180
                );
181
            } else {
182 3
                $tree[$key] = self::createFromArray([
183 3
                    'tmp_name' => $tmpNames[$key],
184 3
                    'size' => $sizes[$key],
185 3
                    'error' => $errors[$key],
186 3
                    'name' => $names[$key] ?? null,
187 3
                    'type' => $types[$key] ?? null,
188
                ]);
189
            }
190
        }
191
192 3
        return $tree;
193
    }
194
}
195