Test Failed
Push — master ( 7b17f2...cf58f0 )
by Julien
13:02
created

mb_sprintf()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
ccs 1
cts 1
cp 1
crap 1
rs 10
1
<?php
2
3
if (!function_exists('implode_sprintf')) {
4
    /**
5
     * Will implode an array_map return of the sprintf or mb_sprintf results
6
     */
7
    function implode_sprintf(array $array = [], string $glue = ' ', string $format = '%s', $multibyte = false): string
8
    {
9
        return implode($glue, array_map(function ($value, $key) use ($format, $multibyte) {
10
            return $multibyte
11
                ? mb_sprintf($format, $value, $key)
12
                : sprintf($format, $value, $key);
13
        }, $array, array_keys($array)));
14
    }
15
}
16
17
if (!function_exists('implode_mb_sprintf')) {
18
    /**
19
     * Will implode an array_map return of the mb_sprintf results
20
     */
21
    function implode_mb_sprintf(array $array = [], string $glue = ' ', string $format = '%s'): string
22
    {
23
        return implode_sprintf($array, $glue, $format, true);
24
    }
25
}
26
27
if (!function_exists('sprintfn')) {
28
    /**
29
     * version of sprintf for cases where named arguments are desired (php syntax)
30
     *
31
     * with sprintf: sprintf('second: %2$s ; first: %1$s', '1st', '2nd');
32
     *
33
     * with sprintfn: sprintfn('second: %second$s ; first: %first$s', array(
34
     *  'first' => '1st',
35
     *  'second'=> '2nd'
36
     * ));
37
     *
38
     * @param string $format sprintf format string, with any number of named arguments
39
     * @param array $args array of [ 'arg_name' => 'arg value', ... ] replacements to be made
40
     * @return string|false result of sprintf call, or bool false on error
41
     */
42
    function sprintfn(string $format, array $args = [])
43
    {
44
        // map of argument names to their corresponding sprintf numeric argument value
45
        $array = array_keys($args);
46
        array_unshift($array, 0);
47
        $array = array_flip(array_slice($array, 1, null, true));
48
        
49
        // find the next named argument. each search starts at the end of the previous replacement.
50
        for ($pos = 0; preg_match('/(?<=%)([a-zA-Z_]\w*)(?=\$)/', $format, $match, PREG_OFFSET_CAPTURE, $pos);) {
51
            $position = intval($match[0][1]);
52
            $length = strlen($match[0][0]);
53
            $key = $match[1][0];
54
            
55
            // programmer did not supply a value for the named argument found in the format string
56
            if (!array_key_exists($key, $array)) {
57
                user_error("sprintfn(): Missing argument '${$key}'", E_USER_WARNING);
58
                return false;
59
            }
60
            
61
            // replace the named argument with the corresponding numeric one
62
            $format = substr_replace($format, $replace = $array[$key], $position, $length);
63
            $pos = $position + strlen($replace);
64
            
65
            // skip to end of replacement for next iteration
66
        }
67
        
68
        return vsprintf($format, array_values($args));
0 ignored issues
show
Bug introduced by
It seems like $format can also be of type array; however, parameter $format of vsprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

68
        return vsprintf(/** @scrutinizer ignore-type */ $format, array_values($args));
Loading history...
69
    }
70
}
71
72
if (!function_exists('mb_sprintf')) {
73
    /**
74
     * Return a formatted multibyte string
75
     * A more complete and working version of mb_sprintf and mb_vsprintf.
76
     * It should work with any "ASCII preserving" encoding such as UTF-8 and all the ISO-8859 charsets.
77
     * It handles sign, padding, alignment, width and precision. Argument swapping is not handled.
78
     * @link http://php.net/manual/en/function.sprintf.php#89020
79
     */
80
    function mb_sprintf($format, ...$args): string
81
    {
82 1
        return mb_vsprintf($format, $args);
83
    }
84
}
85
86
if (!function_exists('mb_vsprintf')) {
87
    /**
88
     * Return a formatted string
89
     * It should work with any "ASCII preserving" encoding such as UTF-8 and all the ISO-8859 charsets.
90
     * It handles sign, padding, alignment, width and precision. Argument swapping is not handled.
91
     * Works with all encodings in format and arguments.
92
     * Supported: Sign, padding, alignment, width and precision.
93
     * Not supported: Argument swapping.
94
     * @link http://php.net/manual/en/function.sprintf.php#89020
95
     */
96
    function mb_vsprintf($format, $argv, $encoding = null): string
97
    {
98 1
        if (!is_array($argv)) {
99
            $argv = [$argv];
100
        }
101
        
102 1
        if (is_null($encoding)) {
103 1
            $encoding = mb_internal_encoding();
104
        }
105
        
106
        // Use UTF-8 in the format so we can use the u flag in preg_split
107 1
        $format = mb_convert_encoding($format, 'UTF-8', $encoding);
108 1
        $newFormat = '';
109
        
110
        // build a new format in UTF-8
111 1
        $newArgv = [];
112
        
113
        // unhandled args in unchanged encoding
114 1
        while ($format !== '') {
115
            
116
            // Split the format in two parts: $pre and $post by the first %-directive
117
            // We get also the matched groups
118 1
            $split = preg_split("!\%(\+?)('.|[0 ]|)(-?)([1-9][0-9]*|)(\.[1-9][0-9]*|)([%a-zA-Z])!u", $format, 2, PREG_SPLIT_DELIM_CAPTURE);
0 ignored issues
show
Bug introduced by
It seems like $format can also be of type array; however, parameter $subject of preg_split() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

118
            $split = preg_split("!\%(\+?)('.|[0 ]|)(-?)([1-9][0-9]*|)(\.[1-9][0-9]*|)([%a-zA-Z])!u", /** @scrutinizer ignore-type */ $format, 2, PREG_SPLIT_DELIM_CAPTURE);
Loading history...
119 1
            $pre = $split[0] ?? null;
120 1
            $sign = $split[1] ?? null;
121 1
            $filler = $split[2] ?? null;
122 1
            $align = $split[3] ?? null;
123 1
            $size = $split[4] ?? null;
124 1
            $precision = $split[5] ?? null;
125 1
            $type = $split[6] ?? null;
126 1
            $post = $split[7] ?? null;
127
            
128 1
            $newFormat .= mb_convert_encoding($pre, $encoding, 'UTF-8');
0 ignored issues
show
Bug introduced by
It seems like $pre can also be of type null; however, parameter $string of mb_convert_encoding() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

128
            $newFormat .= mb_convert_encoding(/** @scrutinizer ignore-type */ $pre, $encoding, 'UTF-8');
Loading history...
129
            
130 1
            if ($type === '') {
131
                // didn't match. do nothing. this is the last iteration.
132
            }
133 1
            elseif ($type === '%') {
134
                // an escaped %
135
                $newFormat .= '%%';
136
            }
137 1
            elseif ($type === 's') {
138
                $arg = array_shift($argv);
139
                $arg = mb_convert_encoding($arg, 'UTF-8', $encoding);
140
                $paddingPre = '';
141
                $paddingPost = '';
142
                
143
                // truncate $arg
144
                if ($precision !== '') {
145
                    $precision = intval(substr($precision, 1));
0 ignored issues
show
Bug introduced by
It seems like $precision can also be of type null; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

145
                    $precision = intval(substr(/** @scrutinizer ignore-type */ $precision, 1));
Loading history...
146
                    if ($precision > 0 && mb_strlen($arg, $encoding) > $precision) {
0 ignored issues
show
Bug introduced by
It seems like $arg can also be of type array; however, parameter $string of mb_strlen() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

146
                    if ($precision > 0 && mb_strlen(/** @scrutinizer ignore-type */ $arg, $encoding) > $precision) {
Loading history...
147
                        $arg = mb_substr($precision, 0, $precision, $encoding);
148
                    }
149
                }
150
                
151
                // define padding
152
                if ($size > 0) {
153
                    $argLength = mb_strlen($arg, $encoding);
154
                    if ($argLength < $size) {
155
                        if ($filler === '') {
156
                            $filler = ' ';
157
                        }
158
                        if ($align === '-') {
159
                            $paddingPost = str_repeat($filler, $size - $argLength);
160
                        }
161
                        else {
162
                            $paddingPre = str_repeat($filler, $size - $argLength);
163
                        }
164
                    }
165
                }
166
                
167
                // escape % and pass it forward
168
                $newFormat .= $paddingPre . str_replace('%', '%%', $arg) . $paddingPost;
169
            }
170
            else {
171
                // another type, pass forward
172 1
                $newFormat .= "%$sign$filler$align$size$precision$type";
173 1
                $newArgv[] = array_shift($argv);
174
            }
175
            
176 1
            $format = strval($post);
177
        }
178
        // Convert new format back from UTF-8 to the original encoding
179 1
        $newFormat = mb_convert_encoding($newFormat, $encoding, 'UTF-8');
180 1
        return !empty($newArgv) ? vsprintf($newFormat, $newArgv) : $newFormat;
0 ignored issues
show
Bug introduced by
It seems like $newFormat can also be of type array; however, parameter $format of vsprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

180
        return !empty($newArgv) ? vsprintf(/** @scrutinizer ignore-type */ $newFormat, $newArgv) : $newFormat;
Loading history...
Bug Best Practice introduced by
The expression return ! empty($newArgv)... $newArgv) : $newFormat could return the type array which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
181
    }
182
}
183