Issues (41)

src/Str.php (7 issues)

1
<?php
2
3
namespace Nip\Utility;
4
5
/**
6
 * Class Str
7
 * @package Nip\Utility
8
 */
9
class Str
10
{
11
    /**
12
     * The cache of snake-cased words.
13
     *
14
     * @var array
15
     */
16
    protected static $snakeCache = [];
17
18
    /**
19
     * The cache of camel-cased words.
20
     *
21
     * @var array
22
     */
23
    protected static $camelCache = [];
24
25
    /**
26
     * The cache of studly-cased words.
27
     *
28
     * @var array
29
     */
30
    protected static $studlyCache = [];
31
32
    /**
33
     * Return the remainder of a string after the first occurrence of a given value.
34
     *
35
     * @param   string  $subject
36
     * @param   string  $search
37
     *
38
     * @return string
39
     */
40
    public static function after(string $subject, string $search): string
41
    {
42
        return $search === '' ? $subject : array_reverse(explode($search, $subject, 2))[0];
43
    }
44
45
    /**
46
     * Return the remainder of a string after the last occurrence of a given value.
47
     *
48
     * @param string $subject
49
     * @param string $search
50
     * @return string
51
     */
52
    public static function afterLast($subject, $search)
53
    {
54
        if ($search === '') {
55
            return $subject;
56
        }
57
58
        $position = strrpos($subject, (string)$search);
59
60
        if ($position === false) {
61
            return $subject;
62
        }
63
64
        return substr($subject, $position + strlen($search));
65
    }
66
67
68
    /**
69
     * Get the portion of a string before the first occurrence of a given value.
70
     *
71
     * @param string $subject
72
     * @param string $search
73
     * @return string
74
     */
75
    public static function before($subject, $search)
76
    {
77
        return $search === '' ? $subject : explode($search, $subject)[0];
78
    }
79
80
    /**
81
     * Get the portion of a string before the last occurrence of a given value.
82
     *
83
     * @param string $subject
84
     * @param string $search
85
     * @return string
86
     */
87
    public static function beforeLast($subject, $search)
88
    {
89
        if ($search === '') {
90
            return $subject;
91
        }
92
93
        $pos = mb_strrpos($subject, $search);
94
95
        if ($pos === false) {
96
            return $subject;
97
        }
98
99
        return static::substr($subject, 0, $pos);
100
    }
101
102
    /**
103
     * Convert a value to camel case.
104
     *
105
     * @param string $value
106
     * @return string
107
     */
108
    public static function camel($value)
109
    {
110
        if (isset(static::$camelCache[$value])) {
111
            return static::$camelCache[$value];
112
        }
113
        return static::$camelCache[$value] = lcfirst(static::studly($value));
114
    }
115
116
    /**
117
     * Convert a value to studly caps case.
118
     *
119
     * @param string $value
120
     * @return string
121
     */
122
    public static function studly($value)
123
    {
124
        $key = $value;
125
        if (isset(static::$studlyCache[$key])) {
126
            return static::$studlyCache[$key];
127
        }
128
        $value = ucwords(str_replace(['-', '_'], ' ', $value));
129
        return static::$studlyCache[$key] = str_replace(' ', '', $value);
130
    }
131
132
    /**
133
     * Determine if a given string ends with a given substring.
134
     *
135
     * @param string $haystack
136
     * @param string|array $needles
137
     * @return bool
138
     */
139
    public static function endsWith($haystack, $needles)
140
    {
141
        foreach ((array)$needles as $needle) {
142
            if (substr($haystack, -strlen($needle)) === (string)$needle) {
143
                return true;
144
            }
145
        }
146
        return false;
147
    }
148
149
    /**
150
     * Cap a string with a single instance of a given value.
151
     *
152
     * @param string $value
153
     * @param string $cap
154
     * @return string
155
     */
156
    public static function finish($value, $cap)
157
    {
158
        $quoted = preg_quote($cap, '/');
159
        return preg_replace('/(?:' . $quoted . ')+$/u', '', $value) . $cap;
160
    }
161
162
    /**
163
     * Determine if a given string matches a given pattern.
164
     *
165
     * @param string $pattern
166
     * @param string $value
167
     * @return bool
168
     */
169
    public static function is($pattern, $value)
170
    {
171
        if ($pattern == $value) {
172
            return true;
173
        }
174
        $pattern = preg_quote($pattern, '#');
175
        // Asterisks are translated into zero-or-more regular expression wildcards
176
        // to make it convenient to check if the strings starts with the given
177
        // pattern such as "library/*", making any string check convenient.
178
        $pattern = str_replace('\*', '.*', $pattern);
179
        return (bool)preg_match('#^' . $pattern . '\z#u', $value);
180
    }
181
182
    /**
183
     * Convert a string to kebab case.
184
     *
185
     * @param string $value
186
     * @return string
187
     */
188
    public static function kebab($value)
189
    {
190
        return static::snake($value, '-');
191
    }
192
193
    /**
194
     * Convert a string to snake case.
195
     *
196
     * @param string $value
197
     * @param string $delimiter
198
     * @return string
199
     */
200
    public static function snake($value, $delimiter = '_')
201
    {
202
        $key = $value;
203
        if (isset(static::$snakeCache[$key][$delimiter])) {
204
            return static::$snakeCache[$key][$delimiter];
205
        }
206
        if (!ctype_lower($value)) {
207
            $value = preg_replace('/\s+/u', '', $value);
208
            $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value));
209
        }
210
        return static::$snakeCache[$key][$delimiter] = $value;
211
    }
212
213
    /**
214
     * Convert the given string to lower-case.
215
     *
216
     * @param string $value
217
     * @return string
218
     */
219
    public static function lower($value)
220
    {
221
        return mb_strtolower($value, 'UTF-8');
222
    }
223
224
    /**
225
     * Limit the number of characters in a string.
226
     *
227
     * @param string $value
228
     * @param int $limit
229
     * @param string $end
230
     * @return string
231
     */
232
    public static function limit($value, $limit = 100, $end = '...')
233
    {
234
        if (mb_strwidth($value, 'UTF-8') <= $limit) {
235
            return $value;
236
        }
237
238
        /** @noinspection PhpComposerExtensionStubsInspection */
239
        return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')) . $end;
240
    }
241
242
    /**
243
     * Limit the number of words in a string.
244
     *
245
     * @param string $value
246
     * @param int $words
247
     * @param string $end
248
     * @return string
249
     */
250
    public static function words($value, $words = 100, $end = '...')
251
    {
252
        preg_match('/^\s*+(?:\S++\s*+){1,' . $words . '}/u', $value, $matches);
253
        if (!isset($matches[0]) || static::length($value) === static::length($matches[0])) {
254
            return $value;
255
        }
256
        return rtrim($matches[0]) . $end;
257
    }
258
259
    /**
260
     * Return the length of the given string.
261
     *
262
     * @param string $value
263
     * @return int
264
     */
265
    public static function length($value)
266
    {
267
        return mb_strlen($value);
268
    }
269
270
    /**
271
     * Parse a Class(at)method style callback into class and method.
272
     *
273
     * @param string $callback
274
     * @param string|null $default
275
     * @return array
276
     */
277
    public static function parseCallback($callback, $default = null)
278
    {
279
        return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
280
    }
281
282
    /**
283
     * Determine if a given string contains a given substring.
284
     *
285
     * @param string $haystack
286
     * @param string|array $needles
287
     * @return bool
288
     */
289
    public static function contains($haystack, $needles)
290
    {
291
        foreach ((array)$needles as $needle) {
292
            if ($needle != '' && mb_strpos($haystack, $needle) !== false) {
293
                return true;
294
            }
295
        }
296
        return false;
297
    }
298
299
    /**
300
     * Get the plural form of an English word.
301
     *
302
     * @param   string  $value
303
     * @param   int     $count
304
     *
305
     * @return string
306
     */
307
    public static function plural(string $value, $count = 2): string
0 ignored issues
show
The parameter $count is not used and could be removed. ( Ignorable by Annotation )

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

307
    public static function plural(string $value, /** @scrutinizer ignore-unused */ $count = 2): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
308
    {
309
        if (function_exists('inflector')) {
310
            return inflector()->pluralize($value);
311
        }
312
        throw new \Exception("Plural fuction needs bytic/inflector");
313
    }
314
315
    /**
316
     * Generate a more truly "random" alpha-numeric string.
317
     *
318
     * @param int $length
319
     * @return string
320
     * @throws \Exception
321
     */
322
    public static function random($length = 16): string
323
    {
324
        $string = '';
325
        while (($len = strlen($string)) < $length) {
326
            $size   = $length - $len;
327
            $bytes  = random_bytes($size);
328
            $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
329
        }
330
331
        return $string;
332
    }
333
334
    /**
335
     * Replace a given value in the string sequentially with an array.
336
     *
337
     * @param string $search
338
     * @param array $replace
339
     * @param string $subject
340
     * @return string
341
     */
342
    public static function replaceArray($search, array $replace, $subject)
343
    {
344
        foreach ($replace as $value) {
345
            $subject = static::replaceFirst($search, $value, $subject);
346
        }
347
        return $subject;
348
    }
349
350
    /**
351
     * Replace the first occurrence of a given value in the string.
352
     *
353
     * @param string $search
354
     * @param string $replace
355
     * @param string $subject
356
     * @return string
357
     */
358
    public static function replaceFirst($search, $replace, $subject)
359
    {
360
        $position = strpos($subject, $search);
361
        if ($position !== false) {
362
            return substr_replace($subject, $replace, $position, strlen($search));
0 ignored issues
show
Bug Best Practice introduced by
The expression return substr_replace($s...ition, strlen($search)) also could return the type array which is incompatible with the documented return type string.
Loading history...
363
        }
364
        return $subject;
365
    }
366
367
    /**
368
     * Replace the last occurrence of a given value in the string.
369
     *
370
     * @param string $search
371
     * @param string $replace
372
     * @param string $subject
373
     * @return string
374
     */
375
    public static function replaceLast($search, $replace, $subject)
376
    {
377
        $position = strrpos($subject, $search);
378
        if ($position !== false) {
379
            return substr_replace($subject, $replace, $position, strlen($search));
0 ignored issues
show
Bug Best Practice introduced by
The expression return substr_replace($s...ition, strlen($search)) also could return the type array which is incompatible with the documented return type string.
Loading history...
380
        }
381
        return $subject;
382
    }
383
384
    /**
385
     * Convert the given string to title case.
386
     *
387
     * @param string $value
388
     * @return string
389
     */
390
    public static function title($value)
391
    {
392
        return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
393
    }
394
395
    /**
396
     * Get the singular form of an English word.
397
     *
398
     * @param string $value
399
     * @return string
400
     */
401
    public static function singular($value)
402
    {
403
        return Pluralizer::singular($value);
0 ignored issues
show
The type Nip\Utility\Pluralizer was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
404
    }
405
406
    /**
407
     * Generate a URL friendly "slug" from a given string.
408
     *
409
     * @param string $title
410
     * @param string $separator
411
     * @return string
412
     */
413
    public static function slug($title, $separator = '-')
414
    {
415
        $title = static::ascii($title);
416
        // Convert all dashes/underscores into separator
417
        $flip = $separator == '-' ? '_' : '-';
418
        $title = preg_replace('![' . preg_quote($flip) . ']+!u', $separator, $title);
419
        // Remove all characters that are not the separator, letters, numbers, or whitespace.
420
        $title = preg_replace('![^' . preg_quote($separator) . '\pL\pN\s]+!u', '', mb_strtolower($title));
421
        // Replace all separator characters and whitespace by a single separator
422
        $title = preg_replace('![' . preg_quote($separator) . '\s]+!u', $separator, $title);
423
        return trim($title, $separator);
424
    }
425
426
    /**
427
     * Transliterate a UTF-8 value to ASCII.
428
     *
429
     * @param string $value
430
     * @return string
431
     */
432
    public static function ascii($value)
433
    {
434
        foreach (static::charsArray() as $key => $val) {
435
            $value = str_replace($val, $key, $value);
436
        }
437
        return preg_replace('/[^\x20-\x7E]/u', '', $value);
438
    }
439
440
    /**
441
     * Returns the replacements for the ascii method.
442
     *
443
     * Note: Adapted from Stringy\Stringy.
444
     *
445
     * @see https://github.com/danielstjules/Stringy/blob/2.3.1/LICENSE.txt
446
     *
447
     * @return array
448
     */
449
    protected static function charsArray()
450
    {
451
        static $charsArray;
452
        if (isset($charsArray)) {
453
            return $charsArray;
454
        }
455
        return $charsArray = require dirname(__DIR__) . '/data/charsArray.php';
456
    }
457
458
    /**
459
     * Determine if a given string starts with a given substring.
460
     *
461
     * @param string $haystack
462
     * @param string|array $needles
463
     * @return bool
464
     */
465
    public static function startsWith($haystack, $needles)
466
    {
467
        foreach ((array)$needles as $needle) {
468
            if ($needle != '' && substr($haystack, 0, strlen($needle)) === (string)$needle) {
469
                return true;
470
            }
471
        }
472
        return false;
473
    }
474
475
    /**
476
     * Make a string's first character uppercase.
477
     *
478
     * @param string $string
479
     * @return string
480
     */
481
    public static function ucfirst($string)
482
    {
483
        return static::upper(static::substr($string, 0, 1)) . static::substr($string, 1);
484
    }
485
486
    /**
487
     * Convert the given string to upper-case.
488
     *
489
     * @param string $value
490
     * @return string
491
     */
492
    public static function upper($value)
493
    {
494
        return mb_strtoupper($value, 'UTF-8');
495
    }
496
497
    /**
498
     * Returns the portion of string specified by the start and length parameters.
499
     *
500
     * @param string $string
501
     * @param int $start
502
     * @param int|null $length
503
     * @return string
504
     */
505
    public static function substr($string, $start, $length = null)
506
    {
507
        return mb_substr($string, $start, $length, 'UTF-8');
508
    }
509
510
    /**
511
     * @param $data
512
     * @param bool $strict
513
     * @return bool
514
     */
515
    public static function isSerialized($data, $strict = true)
516
    {
517
        // if it isn't a string, it isn't serialized.
518
        if (!is_string($data)) {
519
            return false;
520
        }
521
        $data = trim($data);
522
        if ('N;' == $data) {
523
            return true;
524
        }
525
        if (strlen($data) < 4) {
526
            return false;
527
        }
528
        if (':' !== $data[1]) {
529
            return false;
530
        }
531
        if ($strict) {
532
            $lastc = substr($data, -1);
533
            if (';' !== $lastc && '}' !== $lastc) {
534
                return false;
535
            }
536
        } else {
537
            $semicolon = strpos($data, ';');
538
            $brace = strpos($data, '}');
539
            // Either ; or } must exist.
540
            if (false === $semicolon && false === $brace) {
541
                return false;
542
            }
543
            // But neither must be in the first X characters.
544
            if (false !== $semicolon && $semicolon < 3) {
545
                return false;
546
            }
547
            if (false !== $brace && $brace < 4) {
548
                return false;
549
            }
550
        }
551
        $token = $data[0];
552
        switch ($token) {
553
            case 's':
554
                if ($strict) {
555
                    if ('"' !== substr($data, -2, 1)) {
556
                        return false;
557
                    }
558
                } elseif (false === strpos($data, '"')) {
559
                    return false;
560
                }
561
            // or else fall through
562
            // no break
563
            case 'a':
564
            case 'O':
565
                return (bool)preg_match("/^{$token}:[0-9]+:/s", $data);
566
            case 'b':
567
            case 'i':
568
            case 'd':
569
                $end = $strict ? '$' : '';
570
                return (bool)preg_match("/^{$token}:[0-9.E-]+;$end/", $data);
571
        }
572
        return false;
573
    }
574
575 9
    /**
576
     * @param $str
577 9
     * @param $first
578 9
     * @param $last
579 9
     * @return string
580 9
     */
581 9
    public static function mask($str, $first = 0, $last = 0)
582
    {
583
        $len = strlen($str);
584
        $toShow = $first + $last;
585
        return substr($str, 0, $len <= $toShow ? 0 : $first)
586
            . str_repeat("*", $len - ($len <= $toShow ? 0 : $toShow))
587
            . substr($str, $len - $last, $len <= $toShow ? 0 : $last);
588
    }
589 3
590
    /**
591 3
     * @param $name
592 3
     * @param string $separator
593 3
     * @return string
594 3
     */
595 3
    public static function initials($name, $separator = '.')
596
    {
597
        $name = str_replace(['-', '_'], ' ', $name);
598 3
        $split = explode(" ", $name);
599
        $initials = [];
600
        foreach ($split as $part) {
601
            $initials[] = ucfirst($part[0]);
602
        }
603
604
        return implode($separator, $initials) . $separator;
605
    }
606
607 5
    /**
608
     * @param string $string
609 5
     * @param bool $return
610 5
     * @param array $params
611 1
     * @return bool|mixed
612
     */
613 4
    public static function isJson(string $string, $return = false, ...$params)
614
    {
615
        $data = json_decode($string, ...$params);
0 ignored issues
show
$params is expanded, but the parameter $associative of json_decode() does not expect variable arguments. ( Ignorable by Annotation )

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

615
        $data = json_decode($string, /** @scrutinizer ignore-type */ ...$params);
Loading history...
616
        if (json_last_error() !== JSON_ERROR_NONE) {
617
            return false;
618
        }
619
        return $return ? $data : true;
620
    }
621
622
    /**
623
     * Determine if the given value is a standard date format.
624
     *
625
     * @param  string  $value
626
     * @return bool
627
     */
628
    public static function isStandardDateFormat($value)
629
    {
630
        return preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match('/^(\d...)-(\d{1,2})$/', $value) returns the type integer which is incompatible with the documented return type boolean.
Loading history...
631
    }
632
633
    /**
634
     * @param $hex
635
     *
636
     * @return string
637
     */
638
    public static function fromHex($hex): string
639
    {
640
        $str = '';
641
        for ($i=0;$i<strlen($hex);$i+=2) {
642
            $str .= chr(hexdec(substr($hex, $i, 2)));
0 ignored issues
show
It seems like hexdec(substr($hex, $i, 2)) can also be of type double; however, parameter $codepoint of chr() does only seem to accept integer, 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

642
            $str .= chr(/** @scrutinizer ignore-type */ hexdec(substr($hex, $i, 2)));
Loading history...
643
        }
644
        return $str;
645
    }
646
}
647