Passed
Push — develop ( 8f089d...8c5021 )
by Eric
02:43
created

Generator::generatePhpEnum()   B

Complexity

Conditions 8
Paths 72

Size

Total Lines 45
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 25
c 0
b 0
f 0
nc 72
nop 2
dl 0
loc 45
ccs 27
cts 27
cp 1
crap 8
rs 8.4444
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of Esi\Mimey.
7
 *
8
 * (c) Eric Sizemore <[email protected]>
9
 * (c) Ricardo Boss <[email protected]>
10
 * (c) Ralph Khattar <[email protected]>
11
 *
12
 * This source file is subject to the MIT license. For the full copyright,
13
 * license information, and credits/acknowledgements, please view the LICENSE
14
 * and README files that were distributed with this source code.
15
 */
16
/**
17
 * Esi\Mimey is a fork of Elephox\Mimey (https://github.com/elephox-dev/mimey) which is:
18
 *     Copyright (c) 2022 Ricardo Boss
19
 * Elephox\Mimey is a fork of ralouphie/mimey (https://github.com/ralouphie/mimey) which is:
20
 *     Copyright (c) 2016 Ralph Khattar
21
 */
22
23
namespace Esi\Mimey\Mapping;
24
25
use Esi\Mimey\Interface\MimeTypeInterface;
26
use JsonException;
27
use RuntimeException;
28
29
use function array_filter;
30
use function array_unique;
31
use function array_values;
32
use function explode;
33
use function file_get_contents;
34
use function json_encode;
35
use function preg_replace;
36
use function sprintf;
37
use function str_pad;
38
use function str_replace;
39
use function trim;
40
use function ucfirst;
41
use function ucwords;
42
43
use const JSON_PRETTY_PRINT;
44
use const JSON_THROW_ON_ERROR;
45
use const STR_PAD_LEFT;
46
47
/**
48
 * Generates a mapping for use in the MimeTypes class.
49
 *
50
 * Reads text in the format of httpd's mime.types and generates a PHP array containing the mappings.
51
 *
52
 * The psalm-type looks gnarly, but it covers just about everything.
53
 *
54
 * @phpstan-type MimeTypeMap = array{
55
 *    mimes?: array<
56
 *        non-empty-string|string, list<non-empty-string>|array<int<0, max>, string>
57
 *    >|non-empty-array<
58
 *        non-falsy-string|string, non-empty-array<0, non-falsy-string>|array<int<0, max>, string>
59
 *    >,
60
 *    extensions?: array<
61
 *        non-empty-string, list<non-empty-string>
62
 *     >|non-empty-array<
63
 *        string|non-falsy-string, array<int<0, max>, string>|non-empty-array<0, non-falsy-string>
64
 *    >|array<
65
 *        string, array<int<0, max>, string>
66
 *    >
67
 * }
68
 */
69
class Generator
70
{
71
    /**
72
     * @var MimeTypeMap
73
     */
74
    protected array $mapCache = [];
75
76
    /**
77
     * Create a new generator instance with the given mime.types text.
78
     *
79
     * @param non-empty-string $mimeTypesText The text from the mime.types file.
80
     */
81 5
    public function __construct(protected string $mimeTypesText) {}
82
83
    /**
84
     * Generate the JSON from the mapCache.
85
     *
86
     * @param bool $minify Whether to minify the generated JSON.
87
     *
88
     * @throws JsonException
89
     *
90
     * @return non-empty-string
91
     */
92 1
    public function generateJson(bool $minify = true): string
93
    {
94 1
        return json_encode($this->generateMapping(), flags: JSON_THROW_ON_ERROR | ($minify ? 0 : JSON_PRETTY_PRINT));
95
    }
96
97
    /**
98
     * Read the given mime.types text and return a mapping compatible with the MimeTypes class.
99
     *
100
     * @return MimeTypeMap The mapping.
101
     */
102 5
    public function generateMapping(): array
103
    {
104 5
        if ($this->mapCache !== []) {
105 1
            return $this->mapCache;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->mapCache returns the type array which is incompatible with the documented return type Esi\Mimey\Mapping\MimeTypeMap.
Loading history...
106
        }
107
108 5
        $lines = explode("\n", $this->mimeTypesText);
109
110 5
        foreach ($lines as $line) {
111 5
            $line = preg_replace('~#.*~', '', $line) ?? $line;
112 5
            $line = trim($line);
113
114 5
            if ($line === '') {
115 5
                continue;
116
            }
117
118 4
            $parts = array_values(array_filter(
119 4
                explode("\t", $line),
120 4
                static fn (string $value): bool => trim($value) !== ''
121 4
            ));
122
123 4
            if (\count($parts) === 2) {
124 4
                $mime       = trim($parts[0]);
125 4
                $extensions = explode(' ', $parts[1]);
126
127 4
                foreach ($extensions as $extension) {
128 4
                    $extension = trim($extension);
129
130 4
                    if ($mime !== '' && $extension !== '') {
131 4
                        $this->mapCache['mimes'][$extension][] = $mime;
132 4
                        $this->mapCache['extensions'][$mime][] = $extension;
133 4
                        $this->mapCache['mimes'][$extension]   = array_unique($this->mapCache['mimes'][$extension]);
134 4
                        $this->mapCache['extensions'][$mime]   = array_unique($this->mapCache['extensions'][$mime]);
135
136
                    }
137
                }
138
            }
139
        }
140
141 5
        return $this->mapCache;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->mapCache returns the type array which is incompatible with the documented return type Esi\Mimey\Mapping\MimeTypeMap.
Loading history...
142
    }
143
144
    /**
145
     * Generates the PHP Enum found in `dist`.
146
     *
147
     * @param string $classname
148
     * @param string $namespace
149
     *
150
     * @throws RuntimeException
151
     *
152
     * @return string
153
     */
154 3
    public function generatePhpEnum(string $classname = 'MimeType', string $namespace = 'Esi\Mimey'): string
155
    {
156 3
        if ($classname === '') {
157 1
            $classname = 'MimeType';
158
        }
159
160 3
        if ($namespace === '') {
161 1
            $namespace = 'Esi\Mimey';
162
        }
163
164 3
        $values = [
165 3
            'namespace'       => $namespace,
166 3
            'classname'       => $classname,
167 3
            'interface_usage' => $namespace !== __NAMESPACE__ ? ('use ' . MimeTypeInterface::class . ";\n") : '',
168 3
            'cases'           => '',
169 3
            'type2ext'        => '',
170 3
            'ext2type'        => '',
171 3
        ];
172
173 3
        $stubContents = (string) file_get_contents(\dirname(__DIR__, 2) . '/stubs/mimeType.php.stub');
174
175 3
        $mapping = $this->generateMapping();
176
177 3
        $nameMap = [];
178
179 3
        if (!isset($mapping['mimes'], $mapping['extensions'])) {
180 1
            throw new RuntimeException('Unable to generate mapping. Possibly passed malformed $mimeTypesText');
181
        }
182
183 2
        foreach ($mapping['extensions'] as $mime => $extensions) {
184 2
            $nameMap[$mime] = $this->convertMimeTypeToCaseName($mime);
185
186 2
            $values['cases'] .= sprintf(Generator::spaceIndent(4, "case %s = '%s';\n"), $nameMap[$mime], $mime);
187 2
            $values['type2ext'] .= sprintf(Generator::spaceIndent(12, "self::%s => '%s',\n"), $nameMap[$mime], $extensions[0]);
188
        }
189
190 2
        foreach ($mapping['mimes'] as $extension => $mimes) {
191 2
            $values['ext2type'] .= sprintf(Generator::spaceIndent(12, "'%s' => self::%s,\n"), $extension, $nameMap[$mimes[0]]);
192
        }
193
194 2
        foreach ($values as $name => $value) {
195 2
            $stubContents = str_replace("%$name%", $value, $stubContents);
196
        }
197
198 2
        return $stubContents;
199
    }
200
201
    /**
202
     * @param string $mimeType
203
     */
204 2
    protected function convertMimeTypeToCaseName(string $mimeType): string
205
    {
206 2
        if ($mimeType !== '') {
207 2
            $mimeType = preg_replace('/([\/\-_+.]+)/', '', ucfirst(ucwords($mimeType, '/-_+.'))) ?? $mimeType;
208
        }
209
210 2
        return $mimeType;
211
    }
212
213
    /**
214
     * Helper function for self::generatePhpEnum().
215
     */
216 3
    protected static function spaceIndent(int $spaces, string $string): string
217
    {
218 3
        if ($spaces <= 0) {
219 1
            $spaces = 4;
220
        }
221
222 3
        $spaces += \strlen($string);
223
224 3
        return str_pad($string, $spaces, ' ', STR_PAD_LEFT);
225
    }
226
}
227