• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JBZoo / Utils / 29770052588

20 Jul 2026 06:55PM UTC coverage: 92.619% (-0.1%) from 92.722%
29770052588

push

github

web-flow
Merge pull request #57 from JBZoo/release/8.0

8.0.0 — PHP 8.3+ floor & lock-step major

15 of 16 new or added lines in 7 files covered. (93.75%)

106 existing lines in 13 files now uncovered.

1669 of 1802 relevant lines covered (92.62%)

41.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.98
/src/Str.php
1
<?php
2

3
/**
4
 * JBZoo Toolbox - Utils.
5
 *
6
 * This file is part of the JBZoo Toolbox project.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT
11
 * @copyright  Copyright (C) JBZoo.com, All rights reserved.
12
 * @see        https://github.com/JBZoo/Utils
13
 */
14

15
declare(strict_types=1);
16

17
namespace JBZoo\Utils;
18

19
/**
20
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
21
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
22
 * @SuppressWarnings(PHPMD.TooManyMethods)
23
 * @psalm-suppress UnusedClass
24
 */
25
final class Str
26
{
27
    /** Default charset is UTF-8. */
28
    public static string $encoding = 'UTF-8';
29

30
    /**
31
     * Strip all whitespaces from the given string.
32
     * @param string $string The string to strip
33
     */
34
    public static function stripSpace(string $string): string
35
    {
36
        return (string)\preg_replace('/\s+/', '', $string);
12✔
37
    }
38

39
    /**
40
     * Parse text by lines.
41
     */
42
    public static function parseLines(string $text, bool $toAssoc = true): array
43
    {
44
        $text = \htmlspecialchars_decode($text);
12✔
45
        $text = self::clean($text, false, false, false);
12✔
46

47
        $text  = \str_replace(["\n", "\r", "\r\n", \PHP_EOL], "\n", $text);
12✔
48
        $lines = \explode("\n", $text);
12✔
49

50
        $result = [];
12✔
51

52
        foreach ($lines as $line) {
12✔
53
            $line = \trim($line);
12✔
54

55
            if (isStrEmpty($line)) {
12✔
56
                continue;
12✔
57
            }
58

59
            if ($toAssoc) {
12✔
60
                $result[$line] = $line;
12✔
61
            } else {
62
                $result[] = $line;
6✔
63
            }
64
        }
65

66
        return $result;
12✔
67
    }
68

69
    /**
70
     * Make string safe
71
     * - Remove UTF-8 chars
72
     * - Remove all tags
73
     * - Trim
74
     * - Add Slashes (opt)
75
     * - To lower (opt).
76
     */
77
    public static function clean(
78
        string $string,
79
        bool $toLower = false,
80
        bool $addSlashes = false,
81
        bool $removeAccents = true,
82
    ): string {
83
        if ($removeAccents) {
24✔
84
            $string = Slug::removeAccents($string);
12✔
85
        }
86

87
        $string = \strip_tags($string);
24✔
88
        $string = \trim($string);
24✔
89

90
        if ($addSlashes) {
24✔
91
            $string = \addslashes($string);
12✔
92
        }
93

94
        if ($toLower) {
24✔
95
            $string = self::low($string);
12✔
96
        }
97

98
        return $string;
24✔
99
    }
100

101
    /**
102
     * Convert >, <, ', " and & to html entities, but preserves entities that are already encoded.
103
     * @param string $string The text to be converted
104
     */
105
    public static function htmlEnt(string $string, bool $encodedEntities = false): string
106
    {
107
        if ($encodedEntities) {
12✔
108
            $transTable = \get_html_translation_table(\HTML_ENTITIES, \ENT_QUOTES, self::$encoding);
6✔
109

110
            $transTable[\chr(38)] = '&';
6✔
111

112
            $regExp = '/&(?![A-Za-z]{0,4}\w{2,3};|#[\d]{2,3};)/';
6✔
113

114
            return (string)\preg_replace($regExp, '&amp;', \strtr($string, $transTable));
6✔
115
        }
116

117
        return \htmlentities($string, \ENT_QUOTES, self::$encoding);
12✔
118
    }
119

120
    /**
121
     * Get unique string with prefix.
122
     */
123
    public static function unique(string $prefix = 'unique'): string
124
    {
125
        $prefix = \rtrim(\trim($prefix), '-');
6✔
126
        $random = \random_int(10000000, 99999999);
6✔
127

128
        $result = $random;
6✔
129
        if (!isStrEmpty($prefix)) {
6✔
130
            $result = $prefix . '-' . $random;
6✔
131
        }
132

133
        return (string)$result;
6✔
134
    }
135

136
    /**
137
     * Generate readable random string.
138
     */
139
    public static function random(int $length = 10, bool $isReadable = true): string
140
    {
141
        $result = '';
12✔
142

143
        if ($isReadable) {
12✔
144
            $vowels     = ['a', 'e', 'i', 'o', 'u', '0'];
12✔
145
            $consonants = [
12✔
146
                'b',
12✔
147
                'c',
12✔
148
                'd',
12✔
149
                'f',
12✔
150
                'g',
12✔
151
                'h',
12✔
152
                'j',
12✔
153
                'k',
12✔
154
                'l',
12✔
155
                'm',
12✔
156
                'n',
12✔
157
                'p',
12✔
158
                'r',
12✔
159
                's',
12✔
160
                't',
12✔
161
                'v',
12✔
162
                'w',
12✔
163
                'x',
12✔
164
                'y',
12✔
165
                'z',
12✔
166
                '1',
12✔
167
                '2',
12✔
168
                '3',
12✔
169
                '4',
12✔
170
                '5',
12✔
171
                '6',
12✔
172
                '7',
12✔
173
                '8',
12✔
174
                '9',
12✔
175
            ];
12✔
176

177
            $max = $length / 2;
12✔
178

179
            for ($pos = 1; $pos <= $max; $pos++) {
12✔
180
                $result .= $consonants[\random_int(0, \count($consonants) - 1)];
12✔
181
                $result .= $vowels[\random_int(0, \count($vowels) - 1)];
12✔
182
            }
183
        } else {
184
            $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
6✔
185

186
            for ($pos = 0; $pos < $length; $pos++) {
6✔
187
                $result .= $chars[\mt_rand() % \strlen($chars)];
6✔
188
            }
189
        }
190

191
        return $result;
12✔
192
    }
193

194
    /**
195
     * Pads a given string with zeroes on the left.
196
     * @param string $number The number to pad
197
     * @param int    $length The total length of the desired string
198
     */
199
    public static function zeroPad(string $number, int $length): string
200
    {
201
        return \str_pad($number, $length, '0', \STR_PAD_LEFT);
6✔
202
    }
203

204
    /**
205
     * Truncate a string to a specified length without cutting a word off.
206
     * @param string $string The string to truncate
207
     * @param int    $length The length to truncate the string to
208
     * @param string $append Text to append to the string IF it gets truncated, defaults to '...'
209
     */
210
    public static function truncateSafe(string $string, int $length, string $append = '...'): string
211
    {
212
        $result    = self::sub($string, 0, $length);
6✔
213
        $lastSpace = self::rPos($result, ' ');
6✔
214

215
        if ($lastSpace !== null && $string !== $result) {
6✔
216
            $result = self::sub($result, 0, $lastSpace);
6✔
217
        }
218

219
        if ($result !== $string) {
6✔
220
            $result .= $append;
6✔
221
        }
222

223
        return $result;
6✔
224
    }
225

226
    /**
227
     * Truncate the string to given length of characters.
228
     * @param string $string The variable to truncate
229
     * @param int    $limit  The length to truncate the string to
230
     * @param string $append Text to append to the string IF it gets truncated, defaults to '...'
231
     */
232
    public static function limitChars(string $string, int $limit = 100, string $append = '...'): string
233
    {
234
        if (self::len($string) <= $limit) {
6✔
235
            return $string;
6✔
236
        }
237

238
        return \rtrim(self::sub($string, 0, $limit)) . $append;
6✔
239
    }
240

241
    /**
242
     * Truncate the string to given length of words.
243
     */
244
    public static function limitWords(string $string, int $limit = 100, string $append = '...'): string
245
    {
246
        \preg_match('/^\s*+(?:\S++\s*+){1,' . $limit . '}/u', $string, $matches);
6✔
247

248
        if (!\array_key_exists('0', $matches) || self::len($string) === self::len($matches[0])) {
6✔
249
            return $string;
6✔
250
        }
251

252
        return \rtrim($matches[0]) . $append;
6✔
253
    }
254

255
    /**
256
     * Check if a given string matches a given pattern.
257
     * @param string $pattern  Pattern of string expected
258
     * @param string $haystack String that need to be matched
259
     */
260
    public static function like(string $pattern, string $haystack, bool $caseSensitive = true): bool
261
    {
262
        if ($pattern === $haystack) {
6✔
263
            return true;
6✔
264
        }
265

266
        // Preg flags
267
        $flags = $caseSensitive ? '' : 'i';
6✔
268

269
        // Escape any regex special characters
270
        $pattern = \preg_quote($pattern, '#');
6✔
271

272
        // Unescaped * which is our wildcard character and change it to .*
273
        $pattern = \str_replace('\*', '.*', $pattern);
6✔
274

275
        return (bool)\preg_match('#^' . $pattern . '$#' . $flags, $haystack);
6✔
276
    }
277

278
    /**
279
     * Converts any accent characters to their equivalent normal characters.
280
     */
281
    public static function slug(string $text = '', bool $isCache = false): string
282
    {
283
        static $cache = [];
18✔
284

285
        if (!$isCache) {
18✔
286
            return Slug::filter($text);
18✔
287
        }
288

289
        if (!\array_key_exists($text, $cache)) { // Not Arr::key() for performance
×
UNCOV
290
            $cache[$text] = Slug::filter($text);
×
291
        }
292

UNCOV
293
        return $cache[$text];
×
294
    }
295

296
    /**
297
     * Check is mbstring loaded.
298
     */
299
    public static function isMBString(): bool
300
    {
301
        static $isLoaded;
810✔
302

303
        if ($isLoaded === null) {
810✔
304
            $isLoaded = \extension_loaded('mbstring');
6✔
305

306
            if ($isLoaded) {
6✔
307
                \mb_internal_encoding(self::$encoding);
6✔
308
            }
309
        }
310

311
        return $isLoaded;
810✔
312
    }
313

314
    /**
315
     * Get real string length if it's possible.
316
     */
317
    public static function len(string $string): int
318
    {
319
        if (self::isMBString()) {
30✔
320
            return \mb_strlen($string, self::$encoding);
30✔
321
        }
322

UNCOV
323
        return \strlen($string);
×
324
    }
325

326
    /**
327
     * Find position of first occurrence of string in a string.
328
     */
329
    public static function pos(string $haystack, string $needle, int $offset = 0): ?int
330
    {
331
        $result = \strpos($haystack, $needle, $offset);
6✔
332
        if (self::isMBString()) {
6✔
333
            $result = \mb_strpos($haystack, $needle, $offset, self::$encoding);
6✔
334
        }
335

336
        return $result === false ? null : $result;
6✔
337
    }
338

339
    /**
340
     * Find position of last occurrence of a string in a string.
341
     */
342
    public static function rPos(string $haystack, string $needle, int $offset = 0): ?int
343
    {
344
        $result = \strrpos($haystack, $needle, $offset);
12✔
345
        if (self::isMBString()) {
12✔
346
            $result = \mb_strrpos($haystack, $needle, $offset, self::$encoding);
12✔
347
        }
348

349
        return $result === false ? null : $result;
12✔
350
    }
351

352
    /**
353
     * Finds position of first occurrence of a string within another, case-insensitive.
354
     */
355
    public static function iPos(string $haystack, string $needle, int $offset = 0): ?int
356
    {
357
        $result = (int)\stripos($haystack, $needle, $offset);
6✔
358
        if (self::isMBString()) {
6✔
359
            $result = \mb_stripos($haystack, $needle, $offset, self::$encoding);
6✔
360
        }
361

362
        return $result === false ? null : $result;
6✔
363
    }
364

365
    /**
366
     * Finds first occurrence of a string within another.
367
     */
368
    public static function strStr(string $haystack, string $needle, bool $beforeNeedle = false): string
369
    {
370
        if (self::isMBString()) {
6✔
371
            return (string)\mb_strstr($haystack, $needle, $beforeNeedle, self::$encoding);
6✔
372
        }
373

UNCOV
374
        return (string)\strstr($haystack, $needle, $beforeNeedle);
×
375
    }
376

377
    /**
378
     * Finds first occurrence of a string within another, case-insensitive.
379
     */
380
    public static function iStr(string $haystack, string $needle, bool $beforeNeedle = false): string
381
    {
382
        if (self::isMBString()) {
6✔
383
            return (string)\mb_stristr($haystack, $needle, $beforeNeedle, self::$encoding);
6✔
384
        }
385

UNCOV
386
        return (string)\stristr($haystack, $needle, $beforeNeedle);
×
387
    }
388

389
    /**
390
     * Finds the last occurrence of a character in a string within another.
391
     */
392
    public static function rChr(string $haystack, string $needle, bool $part = false): string
393
    {
394
        if (self::isMBString()) {
6✔
395
            return (string)\mb_strrchr($haystack, $needle, $part, self::$encoding);
6✔
396
        }
397

UNCOV
398
        return (string)\strrchr($haystack, $needle);
×
399
    }
400

401
    /**
402
     * Get part of string. Safe alias for substr().
403
     */
404
    public static function sub(string $string, int $start, int $length = 0): string
405
    {
406
        if (self::isMBString()) {
24✔
407
            if ($length === 0) {
24✔
408
                $length = self::len($string);
6✔
409
            }
410

411
            return \mb_substr($string, $start, $length, self::$encoding);
24✔
412
        }
413

UNCOV
414
        return \substr($string, $start, $length);
×
415
    }
416

417
    /**
418
     * Make a string lowercase.
419
     */
420
    public static function low(string $string): string
421
    {
422
        if (self::isMBString()) {
774✔
423
            return \mb_strtolower($string, self::$encoding);
774✔
424
        }
425

UNCOV
426
        return \strtolower($string);
×
427
    }
428

429
    /**
430
     * Make a string uppercase.
431
     * @SuppressWarnings(PHPMD.ShortMethodName)
432
     */
433
    public static function up(string $string): string
434
    {
435
        if (self::isMBString()) {
12✔
436
            return \mb_strtoupper($string, self::$encoding);
12✔
437
        }
438

UNCOV
439
        return \strtoupper($string);
×
440
    }
441

442
    /**
443
     * Count the number of substring occurrences.
444
     */
445
    public static function subCount(string $haystack, string $needle): int
446
    {
447
        if (self::isMBString()) {
6✔
448
            return \mb_substr_count($haystack, $needle, self::$encoding);
6✔
449
        }
450

UNCOV
451
        return \substr_count($haystack, $needle);
×
452
    }
453

454
    /**
455
     * Checks if the $haystack starts with the text in the $needle.
456
     */
457
    public static function isStart(string $haystack, string $needle, bool $caseSensitive = false): bool
458
    {
459
        if ($caseSensitive) {
6✔
460
            return isStrEmpty($needle) || self::pos($haystack, $needle) === 0;
6✔
461
        }
462

463
        return isStrEmpty($needle) || self::iPos($haystack, $needle) === 0;
6✔
464
    }
465

466
    /**
467
     * Checks if the $haystack ends with the text in the $needle. Case-sensitive.
468
     */
469
    public static function isEnd(string $haystack, string $needle, bool $caseSensitive = false): bool
470
    {
471
        if ($caseSensitive) {
6✔
472
            return isStrEmpty($needle) || self::sub($haystack, -self::len($needle)) === $needle;
6✔
473
        }
474

475
        $haystack = self::low($haystack);
6✔
476
        $needle   = self::low($needle);
6✔
477

478
        return isStrEmpty($needle) || self::sub($haystack, -self::len($needle)) === $needle;
6✔
479
    }
480

481
    /**
482
     * Trim whitespaces and other special chars.
483
     */
484
    public static function trim(string $value, bool $extendMode = false): string
485
    {
486
        $result = \trim($value);
660✔
487

488
        if ($extendMode) {
660✔
489
            $result = \trim($result, \chr(0xE3) . \chr(0x80) . \chr(0x80));
6✔
490
            $result = \trim($result, \chr(0xC2) . \chr(0xA0));
6✔
491
            $result = \trim($result);
6✔
492
        }
493

494
        return $result;
660✔
495
    }
496

497
    /**
498
     * Escape string before save it as xml content.
499
     * The function is moved. Please, use \JBZoo\Utils\Xml::escape($string). It'll be deprecated soon.
500
     * @deprecated
501
     */
502
    public static function escXml(string $string): string
503
    {
504
        return Xml::escape($string);
6✔
505
    }
506

507
    /**
508
     * Escape UTF-8 strings.
509
     */
510
    public static function esc(string $string): string
511
    {
512
        return \htmlspecialchars($string, \ENT_NOQUOTES, self::$encoding);
12✔
513
    }
514

515
    /**
516
     * Convert camel case to human-readable format.
517
     */
518
    public static function splitCamelCase(string $input, string $separator = '_', bool $toLower = true): string
519
    {
520
        $original = $input;
6✔
521

522
        $output = (string)\preg_replace(['/(?<=[^A-Z])([A-Z])/', '/(?<=[^0-9])([0-9])/'], '_$0', $input);
6✔
523
        $output = (string)\preg_replace('#_{1,}#', $separator, $output);
6✔
524

525
        $output = \trim($output);
6✔
526
        if ($toLower) {
6✔
527
            $output = \strtolower($output);
6✔
528
        }
529

530
        if (isStrEmpty($output)) {
6✔
UNCOV
531
            return $original;
×
532
        }
533

534
        return $output;
6✔
535
    }
536

537
    /**
538
     * Convert test name to human-readable string.
539
     */
540
    public static function testName2Human(string $input): string
541
    {
542
        $original = $input;
6✔
543
        $input    = self::getClassName($input) ?? '';
6✔
544

545
        /** @noinspection NotOptimalRegularExpressionsInspection */
546
        if (\preg_match('#^tests#i', $input) === 0) {
6✔
547
            $input = (string)\preg_replace('#^(test)#i', '', $input);
6✔
548
        }
549

550
        $input  = (string)\preg_replace('#(test)$#i', '', $input);
6✔
551
        $output = (string)\preg_replace(['/(?<=[^A-Z])([A-Z])/', '/(?<=[^0-9])([0-9])/'], ' $0', $input);
6✔
552
        $output = \str_replace('_', ' ', $output);
6✔
553
        $output = \trim($output);
6✔
554

555
        $output = \implode(
6✔
556
            ' ',
6✔
557
            \array_filter(
6✔
558
                \array_map(static function (string $item): string {
6✔
559
                    $item = \ucwords($item);
6✔
560

561
                    return \trim($item);
6✔
562
                }, \explode(' ', $output)),
6✔
563
            ),
6✔
564
        );
6✔
565

566
        if (\strcasecmp($original, $output) === 0) {
6✔
UNCOV
567
            return $original;
×
568
        }
569

570
        if (isStrEmpty($output)) {
6✔
UNCOV
571
            return $original;
×
572
        }
573

574
        return $output;
6✔
575
    }
576

577
    /**
578
     * Generates a universally unique identifier (UUID v4) according to RFC 4122
579
     * Version 4 UUIDs are pseudo-random!
580
     * Returns Version 4 UUID format: xxxxxxxx-xxxx-4xxx-Yxxx-xxxxxxxxxxxx where x is
581
     * any random hex digit and Y is a random choice from 8, 9, a, or b.
582
     * @see http://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid
583
     */
584
    public static function uuid(): string
585
    {
586
        return \sprintf(
6✔
587
            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
6✔
588
            // 32 bits for "time_low"
589
            \random_int(0, 0xFFFF),
6✔
590
            \random_int(0, 0xFFFF),
6✔
591
            // 16 bits for "time_mid"
592
            \random_int(0, 0xFFFF),
6✔
593
            // 16 bits for "time_hi_and_version",
594
            // four most significant bits holds version number 4
595
            \random_int(0, 0x0FFF) | 0x4000,
6✔
596
            // 16 bits, 8 bits for "clk_seq_hi_res",
597
            // 8 bits for "clk_seq_low",
598
            // two most significant bits holds zero and one for variant DCE1.1
599
            \random_int(0, 0x3FFF) | 0x8000,
6✔
600
            // 48 bits for "node"
601
            \random_int(0, 0xFFFF),
6✔
602
            \random_int(0, 0xFFFF),
6✔
603
            \random_int(0, 0xFFFF),
6✔
604
        );
6✔
605
    }
606

607
    /**
608
     * Get class name without namespace.
609
     */
610
    public static function getClassName(object|string|null $object, bool $toLower = false): ?string
611
    {
612
        if (\is_object($object)) {
12✔
613
            $className = $object::class;
6✔
614
        } elseif (\is_string($object)) {
12✔
615
            $className = $object;
12✔
616
        } else {
617
            return null;
6✔
618
        }
619

620
        $result = $className;
12✔
621

622
        if (\str_contains($className, '\\')) {
12✔
623
            $className = \explode('\\', $className);
12✔
624
            $result    = \end($className);
12✔
625
        }
626

627
        if ($toLower) {
12✔
628
            $result = \strtolower($result);
6✔
629
        }
630

631
        return $result;
12✔
632
    }
633

634
    /**
635
     * Increments a trailing number in a string.
636
     * Used to easily create distinct labels when copying objects. The method has the following styles:
637
     *  - default: "Label" becomes "Label (2)"
638
     *  - dash:    "Label" becomes "Label-2".
639
     * @param string $string the source string
640
     * @param string $style  the style (default|dash)
641
     * @param int    $next   if supplied, this number is used for the copy, otherwise it is the 'next' number
642
     */
643
    public static function inc(string $string, string $style = 'default', int $next = 0): string
644
    {
645
        $styles = [
6✔
646
            'dash'    => ['#-(\d+)$#', '-%d'],
6✔
647
            'default' => [['#\((\d+)\)$#', '#\(\d+\)$#'], [' (%d)', '(%d)']],
6✔
648
        ];
6✔
649

650
        $styleSpec = $styles[$style] ?? $styles['default'];
6✔
651

652
        // Regular expression search and replace patterns.
653
        if (\is_array($styleSpec[0])) {
6✔
654
            $rxSearch = $styleSpec[0][0];
6✔
655

656
            /** @noinspection MultiAssignmentUsageInspection */
657
            $rxReplace = $styleSpec[0][1];
6✔
658
        } else {
659
            $rxSearch = $rxReplace = $styleSpec[0];
6✔
660
        }
661

662
        // New and old (existing) sprintf formats.
663
        if (\is_array($styleSpec[1])) {
6✔
664
            $newFormat = $styleSpec[1][0];
6✔
665

666
            /** @noinspection MultiAssignmentUsageInspection */
667
            $oldFormat = $styleSpec[1][1];
6✔
668
        } else {
669
            $newFormat = $oldFormat = $styleSpec[1];
6✔
670
        }
671

672
        // Check if we are incrementing an existing pattern, or appending a new one.
673
        if (\preg_match($rxSearch, $string, $matches) > 0) {
6✔
674
            $next   = $next === 0 ? ((int)$matches[1] + 1) : $next;
6✔
675
            $string = (string)\preg_replace($rxReplace, \sprintf($oldFormat, $next), $string);
6✔
676
        } else {
677
            $next = $next === 0 ? 2 : $next;
6✔
678
            $string .= \sprintf($newFormat, $next);
6✔
679
        }
680

681
        return $string;
6✔
682
    }
683

684
    /**
685
     * Splits a string of multiple queries into an array of individual queries.
686
     * Single line or line end comments and multi line comments are stripped off.
687
     * @param string $sql input SQL string with which to split into individual queries
688
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
689
     * @SuppressWarnings(PHPMD.NPathComplexity)
690
     */
691
    public static function splitSql(string $sql): array
692
    {
693
        $start     = 0;
6✔
694
        $open      = false;
6✔
695
        $comment   = false;
6✔
696
        $endString = '';
6✔
697
        $end       = \strlen($sql);
6✔
698
        $queries   = [];
6✔
699
        $query     = '';
6✔
700

701
        for ($strIndex = 0; $strIndex < $end; $strIndex++) {
6✔
702
            $current      = $sql[$strIndex];
6✔
703
            $current2     = \substr($sql, $strIndex, 2);
6✔
704
            $current3     = \substr($sql, $strIndex, 3);
6✔
705
            $lenEndString = \strlen($endString);
6✔
706
            $testEnd      = \substr($sql, $strIndex, $lenEndString);
6✔
707

708
            $quotedWithBackslash = $current === '"' || $current === "'" || $current2 === '--'
6✔
709
                || ($current2 === '/*' && $current3 !== '/*!' && $current3 !== '/*+')
6✔
710
                || ($current === '#' && $current3 !== '#__')
6✔
711
                || ($comment && $testEnd === $endString);
6✔
712

713
            if ($quotedWithBackslash) {
6✔
714
                // Check if quoted with previous backslash
715
                $num = 2;
6✔
716

717
                while ($sql[$strIndex - $num + 1] === '\\' && $num < $strIndex) {
6✔
UNCOV
718
                    $num++;
×
719
                }
720

721
                // Not quoted
722
                if ($num % 2 === 0) {
6✔
723
                    if ($open) {
6✔
724
                        if ($testEnd === $endString) {
6✔
725
                            if ($comment) {
6✔
726
                                $comment = false;
6✔
727
                                if ($lenEndString > 1) {
6✔
728
                                    $strIndex += ($lenEndString - 1);
×
UNCOV
729
                                    $current = $sql[$strIndex];
×
730
                                }
731
                                $start = $strIndex + 1;
6✔
732
                            }
733
                            $open      = false;
6✔
734
                            $endString = '';
6✔
735
                        }
736
                    } else {
737
                        $open = true;
6✔
738
                        if ($current2 === '--') {
6✔
739
                            $endString = "\n";
6✔
740
                            $comment   = true;
6✔
741
                        } elseif ($current2 === '/*') {
×
742
                            $endString = '*/';
×
743
                            $comment   = true;
×
744
                        } elseif ($current === '#') {
×
745
                            $endString = "\n";
×
UNCOV
746
                            $comment   = true;
×
747
                        } else {
UNCOV
748
                            $endString = $current;
×
749
                        }
750
                        if ($comment && $start < $strIndex) {
6✔
751
                            $length = \max($strIndex - $start, 0);
6✔
752
                            $query .= \substr($sql, $start, $length);
6✔
753
                        }
754
                    }
755
                }
756
            }
757

758
            if ($comment) {
6✔
759
                $start = $strIndex + 1;
6✔
760
            }
761

762
            if (($current === ';' && !$open) || $strIndex === $end - 1) {
6✔
763
                if ($start <= $strIndex) {
6✔
764
                    $query .= \substr($sql, $start, $strIndex - $start + 1);
6✔
765
                }
766
                $query = \trim($query);
6✔
767

768
                if (!isStrEmpty($query)) {
6✔
769
                    if ($current !== ';') {
6✔
UNCOV
770
                        $query .= ';';
×
771
                    }
772
                    $queries[] = $query;
6✔
773
                }
774

775
                $query = '';
6✔
776
                $start = $strIndex + 1;
6✔
777
            }
778
        }
779

780
        return $queries;
6✔
781
    }
782

783
    /**
784
     * Convert array of strings to list as pretty print description.
785
     */
786
    public static function listToDescription(array $data, bool $alignByKeys = false): ?string
787
    {
788
        $maxWidth = \array_reduce(\array_keys($data), static function ($acc, $key) use ($data): int {
6✔
789
            if (isStrEmpty((string)$data[$key])) {
6✔
790
                return $acc;
6✔
791
            }
792

793
            if ($acc < self::len((string)$key)) {
6✔
794
                $acc = self::len((string)$key);
6✔
795
            }
796

797
            return $acc;
6✔
798
        }, 0);
6✔
799

800
        $result = [];
6✔
801

802
        foreach ($data as $key => $value) {
6✔
803
            $value = \trim((string)$value);
6✔
804
            $key   = \trim((string)$key);
6✔
805

806
            if (!isStrEmpty($value)) {
6✔
807
                $keyFormated = $key;
6✔
808
                if ($alignByKeys) {
6✔
809
                    $keyFormated = $key . \str_repeat(' ', $maxWidth - self::len($key));
6✔
810
                }
811

812
                if (\is_numeric($key) || isStrEmpty($key)) {
6✔
813
                    $result[] = $value;
6✔
814
                } else {
815
                    $result[] = \ucfirst($keyFormated) . ': ' . $value;
6✔
816
                }
817
            }
818
        }
819

820
        if (\count($result) === 0) {
6✔
UNCOV
821
            return null;
×
822
        }
823

824
        return \implode("\n", $result) . "\n";
6✔
825
    }
826

827
    /**
828
     * Extend version of checking if potetielly empty string is empty.
829
     */
830
    public static function isEmpty(bool|string|null $value, bool $strict = false): bool
831
    {
832
        if ($value === null || $value === false) {
978✔
833
            return true;
42✔
834
        }
835

836
        if ($value === true) {
972✔
837
            return false;
6✔
838
        }
839

840
        if (!$strict) {
972✔
841
            $value = \trim($value);
972✔
842
        }
843

844
        return $value === '';
972✔
845
    }
846
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc