Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
152 / 152
100.00% covered (success)
100.00%
11 / 11
CRAP
100.00% covered (success)
100.00%
1 / 1
Str
100.00% covered (success)
100.00%
152 / 152
100.00% covered (success)
100.00%
11 / 11
68
100.00% covered (success)
100.00%
1 / 1
 createSlug
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 createLinks
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 createRandom
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 createRandomAlphaNum
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 createRandomAlpha
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 createRandomNumeric
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 generateRandomString
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 __callStatic
100.00% covered (success)
100.00%
77 / 77
100.00% covered (success)
100.00%
1 / 1
36
 convertFromCamelCase
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 convertToCamelCase
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 detectSeparator
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Utils;
15
16/**
17 * Pop utils string helper class
18 *
19 * @category   Pop
20 * @package    Pop\Utils
21 * @author     Nick Sagona, III <dev@nolainteractive.com>
22 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
23 * @license    http://www.popphp.org/license     New BSD License
24 * @version    2.1.0
25 */
26class Str
27{
28
29    /**
30     * Constants case type for random string generation
31     */
32    const MIXEDCASE = 0;
33    const LOWERCASE = 1;
34    const UPPERCASE = 2;
35
36    /**
37     * Characters for random string generation (certain characters omitted to eliminate confusion)
38     * @var array
39     */
40    protected static array $randomChars = [
41        'abcdefghjkmnpqrstuvwxyz',
42        'ABCDEFGHJKLMNPQRSTUVWXYZ',
43        '0123456789',
44        '!?#$%&@-_+*=,.:;()[]{}',
45    ];
46
47    /**
48     * Regex patterns & replacements for links
49     * @var array
50     */
51    protected static array $linksRegex = [
52        [
53            'pattern'     => '/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/m',
54            'replacement' => '<a href="$0">$0</a>'
55        ],
56        [
57            'pattern'     => '/[a-zA-Z0-9\.\-\_+%]+@[a-zA-Z0-9\-\_\.]+\.[a-zA-Z]{2,4}/m',
58            'replacement' => '<a href="mailto:$0">$0</a>'
59        ]
60    ];
61
62    /**
63     * Allowed keywords for converting cases
64     * @var array
65     */
66    protected static array $allowedCases = [
67        'titlecase', 'camelcase', 'kebabcase', 'dash', 'snakecase', 'underscore', 'namespace', 'path', 'url', 'uri'
68    ];
69
70    /**
71     * Convert the string into an SEO-friendly slug.
72     *
73     * @param  string $string
74     * @param  string $separator
75     * @return string
76     */
77    public static function createSlug(string $string, string $separator = '-'): string
78    {
79        $string = str_replace(' ', $separator, preg_replace('/([^a-zA-Z0-9 \-\/])/', '', strtolower($string)));
80        $regex  = '/' . $separator . '*' . $separator .'/';
81
82        return preg_replace($regex, $separator, $string);
83    }
84
85    /**
86     * Convert any links in the string to HTML links.
87     *
88     * @param  string $string
89     * @param  array  $attributes
90     * @return string
91     */
92    public static function createLinks(string $string, array $attributes = []): string
93    {
94        foreach (self::$linksRegex as $regex) {
95            $replacement = $regex['replacement'];
96
97            if (!empty($attributes)) {
98                $attribs = [];
99                foreach ($attributes as $attrib => $value) {
100                    $attribs[] = $attrib . '="' . $value . '"';
101                }
102                $replacement = str_replace('<a ', '<a ' . implode(' ', $attribs) . ' ', $replacement);
103            }
104
105            $string = preg_replace($regex['pattern'], $replacement, $string);
106        }
107
108        return $string;
109    }
110
111    /**
112     * Generate a random string of a predefined length.
113     *
114     * @param  int $length
115     * @param  int $case
116     * @return string
117     */
118    public static function createRandom(int $length, int $case = self::MIXEDCASE): string
119    {
120        $chars    = self::$randomChars;
121        $charsets = [];
122
123        switch ($case) {
124            case 1:
125                unset($chars[1]);
126                break;
127            case 2:
128                unset($chars[0]);
129                break;
130        }
131
132        foreach ($chars as $key => $value) {
133            $charsets[] = str_split($value);
134        }
135
136        return self::generateRandomString($length, $charsets);
137    }
138
139    /**
140     * Generate a random alphanumeric string of a predefined length.
141     *
142     * @param  int $length
143     * @param  int $case
144     * @return string
145     */
146    public static function createRandomAlphaNum(int $length, int $case = self::MIXEDCASE): string
147    {
148        $chars    = self::$randomChars;
149        $charsets = [];
150
151        switch ($case) {
152            case 1:
153                unset($chars[1]);
154                break;
155            case 2:
156                unset($chars[0]);
157                break;
158        }
159        unset($chars[3]);
160
161        foreach ($chars as $key => $value) {
162            $charsets[] = str_split($value);
163        }
164
165        return self::generateRandomString($length, $charsets);
166    }
167
168    /**
169     * Generate a random alphabetical string of a predefined length.
170     *
171     * @param  int $length
172     * @param  int $case
173     * @return string
174     */
175    public static function createRandomAlpha(int $length, int $case = self::MIXEDCASE): string
176    {
177        $chars    = self::$randomChars;
178        $charsets = [];
179
180        switch ($case) {
181            case 1:
182                unset($chars[1]);
183                break;
184            case 2:
185                unset($chars[0]);
186                break;
187        }
188        unset($chars[2]);
189        unset($chars[3]);
190
191        foreach ($chars as $key => $value) {
192            $charsets[] = str_split($value);
193        }
194
195        return self::generateRandomString($length, $charsets);
196    }
197
198    /**
199     * Generate a random numeric string of a predefined length.
200     *
201     * @param  int $length
202     * @return string
203     */
204    public static function createRandomNumeric(int $length): string
205    {
206        return self::generateRandomString($length, [str_split(self::$randomChars[2])]);
207    }
208
209    /**
210     * Generate characters based on length and character sets provided
211     *
212     * @param  int   $length
213     * @param  array $charsets
214     * @return string
215     */
216    public static function generateRandomString(int $length, array $charsets): string
217    {
218        $string  = '';
219        $indices = array_keys($charsets);
220
221        for ($i = 0; $i < $length; $i++) {
222            $index    = $indices[rand(0, (count($indices) - 1))];
223            $subIndex = rand(0, (count($charsets[$index]) - 1));
224            $string  .= $charsets[$index][$subIndex];
225        }
226
227        return $string;
228    }
229
230    /**
231     * Convert a string from one case to another
232     *
233     * @param string $name
234     * @param array  $arguments
235     * @return string
236     */
237    public static function __callStatic(string $name, array $arguments): string
238    {
239        [$from, $to]   = explode('to', strtolower($name));
240        $string        = $arguments[0] ?? null;
241        $preserveCase  = (array_key_exists(1, $arguments) && is_bool($arguments[1])) ? $arguments[1] : null;
242        $separator     = null;
243        $prevSeparator = null;
244        $result        = null;
245
246        switch ($to) {
247            case 'titlecase':
248            case 'camelcase':
249                $preserveCase = true;
250                break;
251            case 'kebabcase':
252            case 'dash':
253                $separator = '-';
254                if ($preserveCase === null) {
255                    $preserveCase = false;
256                }
257                break;
258            case 'snakecase':
259            case 'underscore':
260                $separator = '_';
261                if ($preserveCase === null) {
262                    $preserveCase = false;
263                }
264                break;
265            case 'namespace':
266                $separator = '\\';
267                if ($preserveCase === null) {
268                    $preserveCase = true;
269                }
270                break;
271            case 'path':
272                $separator = DIRECTORY_SEPARATOR;
273                if ($preserveCase === null) {
274                    $preserveCase = true;
275                }
276                break;
277            case 'uri':
278            case 'url':
279                $separator = '/';
280                if ($preserveCase === null) {
281                    $preserveCase = true;
282                }
283                break;
284        }
285
286        switch ($from) {
287            case 'titlecase':
288            case 'camelcase':
289                $result = self::convertFromCamelCase($string, $separator, $preserveCase);
290                if ($to == 'titlecase') {
291                    $result = ucfirst($result);
292                }
293                if ($to == 'camelcase') {
294                    $result = lcfirst($result);
295                }
296                break;
297            case 'kebabcase':
298            case 'dash':
299                $prevSeparator = '-';
300                break;
301            case 'snakecase':
302            case 'underscore':
303                $prevSeparator = '_';
304                break;
305            case 'namespace':
306                $prevSeparator = '\\';
307                break;
308            case 'path':
309                $prevSeparator = DIRECTORY_SEPARATOR;
310                break;
311            case 'url':
312            case 'uri':
313                $prevSeparator = '/';
314                break;
315        }
316
317        if ($result === null) {
318            switch ($to) {
319                case 'titlecase':
320                    $result = ucfirst(self::convertToCamelCase($string, $prevSeparator));
321                    break;
322                case 'camelcase':
323                    $result = lcfirst(self::convertToCamelCase($string, $prevSeparator));
324                    break;
325                default:
326                    if ($preserveCase) {
327                        $string = implode($prevSeparator, array_map('ucfirst', explode($prevSeparator, $string)));
328                    }
329                    $result = str_replace($prevSeparator, $separator, $string);
330                    if ($preserveCase === false) {
331                        $result = strtolower($result);
332                    }
333            }
334        }
335
336        return $result;
337    }
338
339    /**
340     * Convert a camelCase string using the $separator value passed
341     *
342     * @param string  $string
343     * @param ?string $separator
344     * @param bool    $preserveCase
345     * @return string
346     */
347    public static function convertFromCamelCase(string $string, ?string $separator = null, bool $preserveCase = false): string
348    {
349        $stringAry = str_split($string);
350        $converted = null;
351
352        foreach ($stringAry as $i => $char) {
353            $converted .= ($i == 0) ?
354                $char : ((ctype_upper($char)) ? ($separator . $char) : $char);
355        }
356
357        return ($preserveCase) ? $converted : strtolower($converted);
358    }
359
360    /**
361     * Convert a camelCase string using the $separator value passed
362     *
363     * @param  string  $string
364     * @param  ?string $separator
365     * @return string
366     */
367    public static function convertToCamelCase(string $string, ?string $separator = null): string
368    {
369        if ($separator === null) {
370            $separator = self::detectSeparator($string);
371        }
372        $stringAry = explode($separator, $string);
373        $converted = null;
374
375        foreach ($stringAry as $i => $word) {
376            $converted .= ($i == 0) ? $word : ucfirst($word);
377        }
378
379        return $converted;
380    }
381
382    /**
383     * Attempt to detect separator
384     *
385     * @param  string  $string
386     * @return string
387     */
388    public static function detectSeparator(string $string): string
389    {
390        $separator  = '';
391        $separators = ['-', '_', '\\', '/', DIRECTORY_SEPARATOR];
392
393        foreach ($separators as $s) {
394            if (str_contains($string, $s)) {
395                return $s;
396            }
397        }
398
399        return $separator;
400    }
401
402}
403