Lines 100.00% 13 / 13
Methods 100.00% 2 / 2
Classes 100.00% 1 / 1
Covered by tests of size
Name Lines Methods CRAP
 slug 100.00% 11 / 11 100.00% 1 / 1 1
 replacePattern 100.00% 2 / 2 100.00% 1 / 1 2
24final class TitleSlugifier
25{
26    private const string REGEX_MULTI_DASH = '/-{2,}/m';
27
28    private const string REGEX_INNER_DASH = '/(?<=\w)-(?=\w)/m';
29
30    private const string REGEX_WHITESPACE = '/\s+/m';
31
32    private const array PUNCTUATION = [
33        '+',
34        ',',
35        ';',
36        ':',
37        '.',
38        '?',
39        '!',
40        '"',
41        '(',
42        ')',
43        '[',
44        ']',
45        '{',
46        '}',
47        '<',
48        '>',
49        '%',
50    ];
51
52    private const array UMLAUTS = [
53        'à',
54        'è',
55        'é',
56        'ì',
57        'ò',
58        'ù',
59        'ä',
60        'ö',
61        'ü',
62        'ß',
63        'Ä',
64        'Ö',
65        'Ü',
66        'č',
67        'ę',
68        'ė',
69        'į',
70        'š',
71        'ų',
72        'ū',
73        'ž',
74    ];
75
76    private const array UMLAUTS_REPLACEMENTS = [
77        'a',
78        'e',
79        'e',
80        'i',
81        'o',
82        'u',
83        'ae',
84        'oe',
85        'ue',
86        'ss',
87        'Ae',
88        'Oe',
89        'Ue',
90        'c',
91        'e',
92        'e',
93        'i',
94        's',
95        'u',
96        'u',
97        'z',
98    ];
99
100    public static function slug(string $title): string
101    {
102        $itemTitle = trim($title);
103        $itemTitle = Strings::strtolower($itemTitle);
104        // replace apostrophe and slash with underscore
105        $itemTitle = str_replace(["'", '/', '&#39;', '&#039;'], replace: '_', subject: $itemTitle);
106        // collapse multiple dashes to a space (will become '-')
107        $itemTitle = self::replacePattern(self::REGEX_MULTI_DASH, ' ', $itemTitle);
108        // replace single inner dashes with underscores (legacy: HD-Ready => hd_ready)
109        $itemTitle = self::replacePattern(self::REGEX_INNER_DASH, '_', $itemTitle);
110        // whitespace to '-'
111        $itemTitle = self::replacePattern(self::REGEX_WHITESPACE, '-', $itemTitle);
112        // strip punctuation
113        $itemTitle = str_replace(self::PUNCTUATION, replace: '', subject: $itemTitle);
114        // map umlauts and accents
115        $itemTitle = str_replace(self::UMLAUTS, self::UMLAUTS_REPLACEMENTS, $itemTitle);
116        // reduce multiple separators
117        $itemTitle = self::replacePattern('/_{2,}/m', '_', $itemTitle);
118        $itemTitle = self::replacePattern(self::REGEX_MULTI_DASH, '-', $itemTitle);
119        // trim edge separators
120        return trim($itemTitle, characters: '-_');
121    }
122
123    /**
124     * Applies a regex replacement and keeps the input when the engine fails.
125     */
126    private static function replacePattern(string $pattern, string $replacement, string $subject): string
127    {
128        $replaced = Strings::preg_replace($pattern, $replacement, $subject);
129
130        return is_string($replaced) ? $replaced : $subject;
131    }
132}