AbstractStringCase::strpad()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 37
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7.1753

Importance

Changes 0
Metric Value
cc 4
eloc 23
nc 4
nop 4
dl 0
loc 37
ccs 10
cts 24
cp 0.4167
crap 7.1753
rs 9.552
c 0
b 0
f 0
1
<?php
2
3
namespace Mbright\Validation\Rule;
4
5
use Mbright\Validation\Exception\MalformedUtf8Exception;
6
7
class AbstractStringCase
8
{
9
    /**
10
     * Proxy to `mb_convert_case()` when available; fall back to `utf8_decode()` and `strtolower()` otherwise.
11
     *
12
     * @param string $str string to convert case.
13
     *
14
     * @return string
15
     */
16 87
    protected function strtolower($str)
17
    {
18 87
        if ($this->mbstring()) {
19 87
            return mb_convert_case($str, MB_CASE_LOWER, 'UTF-8');
20
        }
21
22
        return strtolower(utf8_decode($str));
23
    }
24
25
    /**
26
     * Proxy to `mb_convert_case()` when available; fall back to `utf8_decode()` and `strtoupper()` otherwise.
27
     *
28
     * @param string $str string to convert case.
29
     *
30
     * @return string
31
     */
32 87
    protected function strtoupper($str)
33
    {
34 87
        if ($this->mbstring()) {
35 87
            return mb_convert_case($str, MB_CASE_UPPER, 'UTF-8');
36
        }
37
38
        return strtoupper(utf8_decode($str));
39
    }
40
41
    /**
42
     * Proxy to `mb_convert_case()` when available; fall back to `utf8_decode()` and `ucwords()` otherwise.
43
     *
44
     * @param string $str string to convert case.
45
     *
46
     * @return int
47
     */
48 42
    protected function ucwords($str)
49
    {
50 42
        if ($this->mbstring()) {
51 42
            return mb_convert_case($str, MB_CASE_TITLE, 'UTF-8');
52
        }
53
54
        return ucwords(utf8_decode($str));
55
    }
56
57
    /**
58
     * Proxy to `mb_convert_case()` when available; fall back to `utf8_decode()` and `strtoupper()` otherwise.
59
     *
60
     * @param string $str string to convert case.
61
     *
62
     * @return int
63
     */
64 45
    protected function ucfirst($str)
65
    {
66 45
        $len = $this->strlen($str);
67 45
        if ($len == 0) {
68 3
            return '';
69
        }
70 42
        if ($len > 1) {
71 36
            $head = $this->substr($str, 0, 1);
72 36
            $tail = $this->substr($str, 1, $len - 1);
73
74 36
            return $this->strtoupper($head) . $tail;
75
        }
76
77 6
        return $this->strtoupper($str);
78
    }
79
80
    /**
81
     * Proxy to `mb_convert_case()` when available; fall back to `utf8_decode()` and `strtolower()` otherwise.
82
     *
83
     * @param string $str string to convert case.
84
     *
85
     * @return int
86
     */
87 45
    protected function lcfirst($str)
88
    {
89 45
        $len = $this->strlen($str);
90 45
        if ($len == 0) {
91 3
            return '';
92
        }
93 42
        if ($len > 1) {
94 36
            $head = $this->substr($str, 0, 1);
95 36
            $tail = $this->substr($str, 1, $len - 1);
96
97 36
            return $this->strtolower($head) . $tail;
98
        }
99
100 6
        return $this->strtolower($str);
101
    }
102
103
    /**
104
     * Is the `mbstring` extension loaded?
105
     *
106
     * @return bool
107
     */
108 216
    protected function mbstring()
109
    {
110 216
        return extension_loaded('mbstring');
111
    }
112
113
    /**
114
     * Is the `iconv` extension loaded?
115
     *
116
     * @return bool
117
     */
118 300
    protected function iconv()
119
    {
120 300
        return extension_loaded('iconv');
121
    }
122
123
    /**
124
     * Proxy to `iconv_strlen()` or `mb_strlen()` when available; fall back to `utf8_decode()` and `strlen()` otherwise.
125
     *
126
     * @param string $str Return the number of characters in this string.
127
     *
128
     * @return int
129
     */
130 300
    protected function strlen($str)
131
    {
132 300
        if ($this->iconv()) {
133 300
            return $this->strlenIconv($str);
134
        }
135
136
        if ($this->mbstring()) {
137
            return mb_strlen($str, 'UTF-8');
138
        }
139
140
        return strlen(utf8_decode($str));
141
    }
142
143
    /**
144
     * Wrapper for `iconv_substr()` to throw an exception on malformed UTF-8.
145
     *
146
     * @param string $str The string to work with.
147
     * @param int $start Start at this position.
148
     * @param int $length End after this many characters.
149
     * @return string
150
     *
151
     * @throws MalformedUtf8Exception
152
     */
153 120
    protected function substrIconv($str, $start, $length)
154
    {
155 120
        $level = error_reporting(0);
156 120
        $substr = iconv_substr($str, $start, $length, 'UTF-8');
157 120
        error_reporting($level);
158
159 120
        if ($substr !== false) {
160 120
            return $substr;
161
        }
162
163
        throw new MalformedUtf8Exception();
164
    }
165
166
    /**
167
     * Wrapper for `iconv_strlen()` to throw an exception on malformed UTF-8.
168
     *
169
     * @param string $str Return the number of characters in this string.
170
     *
171
     * @throws MalformedUtf8Exception
172
     *
173
     * @return int
174
     */
175 300
    protected function strlenIconv($str)
176
    {
177 300
        $level = error_reporting(0);
178 300
        $strlen = iconv_strlen($str, 'UTF-8');
179 300
        error_reporting($level);
180
181 300
        if ($strlen !== false) {
182 300
            return $strlen;
183
        }
184
185
        throw new MalformedUtf8Exception();
186
    }
187
188
    /**
189
     * Proxy to `iconv_substr()` or `mb_substr()` when the `mbstring` available; polyfill via `preg_split()` and
190
     * `array_slice()` otherwise.
191
     *
192
     * @param string $str The string to work with.
193
     * @param int $start Start at this position.
194
     * @param int $length End after this many characters.
195
     *
196
     * @return string
197
     */
198 120
    protected function substr($str, $start, $length = null)
199
    {
200 120
        if ($this->iconv()) {
201 120
            return $this->substrIconv($str, $start, $length);
202
        }
203
204
        if ($this->mbstring()) {
205
            return mb_substr($str, $start, $length, 'UTF-8');
206
        }
207
208
        $split = preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY);
209
210
        return implode('', array_slice($split, $start, $length));
0 ignored issues
show
Bug introduced by
It seems like $split can also be of type false; however, parameter $array of array_slice() does only seem to accept array, 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

210
        return implode('', array_slice(/** @scrutinizer ignore-type */ $split, $start, $length));
Loading history...
211
    }
212
213
    /**
214
     * Userland UTF-8-aware implementation of `str_pad()`.
215
     *
216
     * @param string $input The input string.
217
     * @param int $pad_length If the value of pad_length is negative, less than, or equal to the length of the input
218
     * string, no padding takes place.
219
     * @param string $pad_str Pad with this string. The pad_string may be truncated if the required number of padding
220
     * characters can't be evenly divided by the pad_string's length.
221
     * @param int $pad_type Optional argument pad_type can be STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH. If pad_type
222
     * is not specified it is assumed to be STR_PAD_RIGHT.
223
     *
224
     * @return string
225
     */
226 18
    protected function strpad($input, $padLength, $padStr = ' ', $padType = STR_PAD_RIGHT)
227
    {
228 18
        $inputLen = $this->strlen($input);
229 18
        if ($padLength <= $inputLen) {
230
            return $input;
231
        }
232
233 18
        $padStrLen = $this->strlen($padStr);
234 18
        $padLen = $padLength - $inputLen;
235
236 18
        if ($padType == STR_PAD_LEFT) {
237
            $repeatTimes = ceil($padLen / $padStrLen);
238
            $prefix = str_repeat($padStr, $repeatTimes);
0 ignored issues
show
Bug introduced by
$repeatTimes of type double is incompatible with the type integer expected by parameter $multiplier of str_repeat(). ( Ignorable by Annotation )

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

238
            $prefix = str_repeat($padStr, /** @scrutinizer ignore-type */ $repeatTimes);
Loading history...
239
240
            return $this->substr($prefix, 0, floor($padLen)) . $input;
0 ignored issues
show
Bug introduced by
floor($padLen) of type double is incompatible with the type integer expected by parameter $length of Mbright\Validation\Rule\...actStringCase::substr(). ( Ignorable by Annotation )

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

240
            return $this->substr($prefix, 0, /** @scrutinizer ignore-type */ floor($padLen)) . $input;
Loading history...
241
        }
242
243 18
        if ($padType == STR_PAD_BOTH) {
244
            $padLen /= 2;
245
            $padAmountLeft = floor($padLen);
246
            $padAmountRight = ceil($padLen);
247
            $repeatTimesLeft = ceil($padAmountLeft / $padStrLen);
248
            $repeatTimesRight = ceil($padAmountRight / $padStrLen);
249
250
            $prefix = str_repeat($padStr, $repeatTimesLeft);
251
            $paddingLeft = $this->substr($prefix, 0, $padAmountLeft);
252
253
            $suffix = str_repeat($padStr, $repeatTimesRight);
254
            $paddingRight = $this->substr($suffix, 0, $padAmountRight);
255
256
            return $paddingLeft . $input . $paddingRight;
257
        }
258
259
        // STR_PAD_RIGHT
260 18
        $repeatTimes = ceil($padLen / $padStrLen);
261 18
        $input .= str_repeat($padStr, $repeatTimes);
262 18
        return $this->substr($input, 0, $padLength);
263
    }
264
}
265