Generator::generateMapCache()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 12
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 15
ccs 13
cts 13
cp 1
crap 4
rs 9.8666
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\Interfaces\MimeTypeInterface;
26
use JsonException;
27
use RuntimeException;
28
29
use function array_filter;
30
use function array_map;
31
use function array_unique;
32
use function array_values;
33
use function explode;
34
use function file_get_contents;
35
use function json_encode;
36
use function preg_replace;
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 7
    public function __construct(protected readonly 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 7
    public function generateMapping(): array
103
    {
104 7
        if ($this->mapCache !== []) {
105 1
            return $this->mapCache;
106
        }
107
108 7
        $lines = array_filter(array_map(
109 7
            static fn (string $line): string => trim(preg_replace('~#.*~', '', $line) ?? $line),
110 7
            explode("\n", $this->mimeTypesText)
111 7
        ), static fn (string $value): bool => trim($value) !== '');
112
113 7
        foreach ($lines as $line) {
114 5
            $parts = array_values(array_filter(
115 5
                explode("\t", $line),
116 5
                static fn (string $value): bool => trim($value) !== ''
117 5
            ));
118
119 5
            $this->generateMapCache($parts);
120
        }
121
122 7
        return $this->mapCache;
123
    }
124
125
    /**
126
     * Generates the PHP Enum found in `dist`.
127
     *
128
     * @param non-empty-string $classname
129
     * @param non-empty-string $namespace
130
     *
131
     * @throws RuntimeException
132
     */
133 5
    public function generatePhpEnum(string $classname = 'MimeType', string $namespace = 'Esi\Mimey'): string
134
    {
135 5
        $values = [
136 5
            '%namespace%'       => $namespace,
137 5
            '%classname%'       => $classname,
138 5
            '%interface_usage%' => $namespace !== __NAMESPACE__ ? ('use ' . MimeTypeInterface::class . ";\n") : '',
139 5
            '%cases%'           => '',
140 5
            '%type2ext%'        => '',
141 5
            '%ext2type%'        => '',
142 5
        ];
143
144 5
        $stubContents = (string) file_get_contents(\dirname(__DIR__, 2) . '/stubs/mimeType.php.stub');
145
146 5
        $mapping = $this->generateMapping();
147
148 5
        $nameMap = [];
149
150 5
        if (!isset($mapping['mimes'], $mapping['extensions'])) {
151 3
            throw new RuntimeException('Unable to generate mapping. Possibly passed malformed $mimeTypesText');
152
        }
153
154 2
        foreach ($mapping['extensions'] as $mime => $extensions) {
155 2
            $nameMap[$mime] = $this->convertMimeTypeToCaseName($mime);
156
157 2
            $values['%cases%'] .= \sprintf(Generator::spaceIndent(4, "case %s = '%s';\n"), $nameMap[$mime], $mime);
158 2
            $values['%type2ext%'] .= \sprintf(Generator::spaceIndent(12, "self::%s => '%s',\n"), $nameMap[$mime], $extensions[0]);
159
        }
160
161 2
        foreach ($mapping['mimes'] as $extension => $mimes) {
162 2
            $values['%ext2type%'] .= \sprintf(Generator::spaceIndent(12, "'%s' => self::%s,\n"), $extension, $nameMap[$mimes[0]]);
163
        }
164
165 2
        return str_replace(
166 2
            array_keys($values),
167 2
            array_values($values),
168 2
            $stubContents
169 2
        );
170
    }
171
172 2
    protected function convertMimeTypeToCaseName(string $mimeType): string
173
    {
174 2
        return preg_replace('/([\/\-_+.]+)/', '', ucfirst(ucwords($mimeType, '/-_+.'))) ?? $mimeType;
175
    }
176
177
    /**
178
     * Helper function for self::generateMapping().
179
     *
180
     * @param list<string> $parts
181
     */
182 5
    protected function generateMapCache(array $parts): void
183
    {
184 5
        if (\count($parts) === 2) {
185 4
            $mime       = trim($parts[0]);
186 4
            $extensions = array_filter(array_map(
187 4
                static fn (string $extension): string => trim($extension),
188 4
                explode(' ', $parts[1])
189 4
            ), static fn (string $value): bool => $value !== '');
190
191 4
            foreach ($extensions as $extension) {
192 4
                if ($mime !== '') {
193 4
                    $this->mapCache['mimes'][$extension][] = $mime;
194 4
                    $this->mapCache['extensions'][$mime][] = $extension;
195 4
                    $this->mapCache['mimes'][$extension]   = array_unique($this->mapCache['mimes'][$extension]);
196 4
                    $this->mapCache['extensions'][$mime]   = array_unique($this->mapCache['extensions'][$mime]);
197
                }
198
            }
199
        }
200
    }
201
202
    /**
203
     * Helper function for self::generatePhpEnum().
204
     */
205 3
    protected static function spaceIndent(int $spaces, string $string): string
206
    {
207 3
        if ($spaces <= 0) {
208 1
            $spaces = 4;
209
        }
210
211 3
        $spaces += \strlen($string);
212
213 3
        return str_pad($string, $spaces, ' ', STR_PAD_LEFT);
214
    }
215
}
216