├── .editorconfig ├── .gitignore ├── LICENSE ├── Migration.md ├── README.md ├── composer.json ├── composer.lock ├── src └── Str.php └── tests └── index.php /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = tab 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | indent_style = space 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ 2 | .idea/ 3 | 4 | # Composer 5 | vendor/ 6 | composer.phar 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) delight.im (https://www.delight.im/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Migration.md: -------------------------------------------------------------------------------- 1 | # Migration 2 | 3 | ## General 4 | 5 | Update your version of this library using Composer and its `composer update` or `composer require` commands [[?]](https://github.com/delight-im/Knowledge/blob/master/Composer%20(PHP).md#how-do-i-update-libraries-or-modules-within-my-application). 6 | 7 | ## From `v1.x.x` to `v2.x.x` 8 | 9 | * The license has been changed from the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) to the [MIT License](https://opensource.org/licenses/MIT). 10 | * The method `escapeForHtml` now operates on a copy of the original data and thus does not modify its own instance anymore. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP-Str 2 | 3 | Convenient object-oriented operations on strings 4 | 5 | ## Requirements 6 | 7 | * PHP 5.3.0+ 8 | * Multibyte String extension (`mbstring`) 9 | 10 | ## Installation 11 | 12 | 1. Include the library via Composer [[?]](https://github.com/delight-im/Knowledge/blob/master/Composer%20(PHP).md): 13 | 14 | ``` 15 | $ composer require delight-im/str 16 | ``` 17 | 18 | 1. Include the Composer autoloader: 19 | 20 | ```php 21 | require __DIR__ . '/vendor/autoload.php'; 22 | ``` 23 | 24 | ## Usage 25 | 26 | Usually, you'll want to set up the following shorthand in the global namespace of your PHP code: 27 | 28 | ```php 29 | function s($str, $charset = null) { 30 | return new \Delight\Str\Str($str, $charset); 31 | } 32 | ``` 33 | 34 | This lets you create string objects by simply wrapping strings in `s(...)`. 35 | 36 | With that shorthand in place, creating instances from any string is as simple as this: 37 | 38 | ```php 39 | $str = s('Hello w☺rld'); 40 | // or 41 | $str = s('Hello w☺rld', 'UTF-8'); 42 | ``` 43 | 44 | If you don’t want to set up that shorthand, however, you can still create instances easily: 45 | 46 | ```php 47 | $str = \Delight\Str\Str::from('Hello w☺rld'); 48 | // or 49 | $str = \Delight\Str\Str::from('Hello w☺rld', 'UTF-8'); 50 | ``` 51 | 52 | ### Available methods 53 | 54 | * `startsWith` 55 | * `startsWithBytes` 56 | * `startsWithCodePoints` 57 | * `startsWithIgnoreCase` 58 | * `startsWithBytesIgnoreCase` 59 | * `startsWithCodePointsIgnoreCase` 60 | * `contains` 61 | * `containsBytes` 62 | * `containsCodePoints` 63 | * `containsIgnoreCase` 64 | * `containsBytesIgnoreCase` 65 | * `containsCodePointsIgnoreCase` 66 | * `endsWith` 67 | * `endsWithBytes` 68 | * `endsWithCodePoints` 69 | * `endsWithIgnoreCase` 70 | * `endsWithBytesIgnoreCase` 71 | * `endsWithCodePointsIgnoreCase` 72 | * `trim` 73 | * `trimStart` 74 | * `trimEnd` 75 | * `first` 76 | * `firstBytes` 77 | * `firstCodePoints` 78 | * `last` 79 | * `lastBytes` 80 | * `lastCodePoints` 81 | * `byteAt` 82 | * `codePointAt` 83 | * `isEmpty` 84 | * `isAscii` 85 | * `isPrintableAscii` 86 | * `toLowerCase` 87 | * `toLowerCaseBytes` 88 | * `toLowerCaseCodePoints` 89 | * `isLowerCase` 90 | * `toUpperCase` 91 | * `toUpperCaseBytes` 92 | * `toUpperCaseCodePoints` 93 | * `isUpperCase` 94 | * `isCapitalized` 95 | * `truncate` 96 | * `truncateBytes` 97 | * `truncateCodePoints` 98 | * `truncateSafely` 99 | * `truncateBytesSafely` 100 | * `truncateCodePointsSafely` 101 | * `count` 102 | * `countBytes` 103 | * `countCodePoints` 104 | * `length` 105 | * `lengthInBytes` 106 | * `lengthInCodePoints` 107 | * `cutStart` 108 | * `cutBytesAtStart` 109 | * `cutCodePointsAtStart` 110 | * `cutEnd` 111 | * `cutBytesAtEnd` 112 | * `cutCodePointsAtEnd` 113 | * `replace` 114 | * `replaceBytes` 115 | * `replaceCodePoints` 116 | * `replaceIgnoreCase` 117 | * `replaceBytesIgnoreCase` 118 | * `replaceCodePointsIgnoreCase` 119 | * `replaceFirst` 120 | * `replaceFirstBytes` 121 | * `replaceFirstCodePoints` 122 | * `replaceFirstIgnoreCase` 123 | * `replaceFirstBytesIgnoreCase` 124 | * `replaceFirstCodePointsIgnoreCase` 125 | * `replacePrefix` 126 | * `replacePrefixBytes` 127 | * `replacePrefixCodePoints` 128 | * `replaceLast` 129 | * `replaceLastBytes` 130 | * `replaceLastCodePoints` 131 | * `replaceLastIgnoreCase` 132 | * `replaceLastBytesIgnoreCase` 133 | * `replaceLastCodePointsIgnoreCase` 134 | * `replaceSuffix` 135 | * `replaceSuffixBytes` 136 | * `replaceSuffixCodePoints` 137 | * `split` 138 | * `splitBytes` 139 | * `splitCodePoints` 140 | * `splitByRegex` 141 | * `words` 142 | * `beforeFirst` 143 | * `beforeFirstBytes` 144 | * `beforeFirstCodePoints` 145 | * `beforeLast` 146 | * `beforeLastBytes` 147 | * `beforeLastCodePoints` 148 | * `between` 149 | * `betweenBytes` 150 | * `betweenCodePoints` 151 | * `afterFirst` 152 | * `afterFirstBytes` 153 | * `afterFirstCodePoints` 154 | * `afterLast` 155 | * `afterLastBytes` 156 | * `afterLastCodePoints` 157 | * `matches` 158 | * `equals` 159 | * `equalsIgnoreCase` 160 | * `compareTo` 161 | * `compareToBytes` 162 | * `compareToCodePoints` 163 | * `compareToIgnoreCase` 164 | * `compareToBytesIgnoreCase` 165 | * `compareToCodePointsIgnoreCase` 166 | * `escapeForHtml` 167 | * `normalizeLineEndings` 168 | * `reverse` 169 | * `acronym` 170 | 171 | ```php 172 | /** 173 | * Returns whether this string starts with the supplied other string 174 | * 175 | * This operation is case-sensitive 176 | * 177 | * @param string $prefix the other string to search for 178 | * @return bool whether the supplied other string can be found at the beginning of this string 179 | */ 180 | function startsWith($prefix); 181 | function startsWithBytes($prefix); 182 | function startsWithCodePoints($prefix); 183 | 184 | 185 | //////////////////////////////////////////////////////////////////////////////// 186 | 187 | 188 | /** 189 | * Returns whether this string starts with the supplied other string 190 | * 191 | * This operation is case-insensitive 192 | * 193 | * @param string $prefix the other string to search for 194 | * @return bool whether the supplied other string can be found at the beginning of this string 195 | */ 196 | function startsWithIgnoreCase($prefix); 197 | function startsWithBytesIgnoreCase($prefix); 198 | function startsWithCodePointsIgnoreCase($prefix); 199 | 200 | 201 | //////////////////////////////////////////////////////////////////////////////// 202 | 203 | 204 | /** 205 | * Returns whether this string contains the supplied other string 206 | * 207 | * This operation is case-sensitive 208 | * 209 | * @param string $infix the other string to search for 210 | * @return bool whether the supplied other string is contained in this string 211 | */ 212 | function contains($infix); 213 | function containsBytes($infix); 214 | function containsCodePoints($infix); 215 | 216 | 217 | //////////////////////////////////////////////////////////////////////////////// 218 | 219 | 220 | /** 221 | * Returns whether this string contains the supplied other string 222 | * 223 | * This operation is case-insensitive 224 | * 225 | * @param string $infix the other string to search for 226 | * @return bool whether the supplied other string is contained in this string 227 | */ 228 | function containsIgnoreCase($infix); 229 | function containsBytesIgnoreCase($infix); 230 | function containsCodePointsIgnoreCase($infix); 231 | 232 | 233 | //////////////////////////////////////////////////////////////////////////////// 234 | 235 | 236 | /** 237 | * Returns whether this string ends with the supplied other string 238 | * 239 | * This operation is case-sensitive 240 | * 241 | * @param string $suffix the other string to search for 242 | * @return bool whether the supplied other string can be found at the end of this string 243 | */ 244 | function endsWith($suffix); 245 | function endsWithBytes($suffix); 246 | function endsWithCodePoints($suffix); 247 | 248 | 249 | //////////////////////////////////////////////////////////////////////////////// 250 | 251 | 252 | /** 253 | * Returns whether this string ends with the supplied other string 254 | * 255 | * This operation is case-insensitive 256 | * 257 | * @param string $suffix the other string to search for 258 | * @return bool whether the supplied other string can be found at the end of this string 259 | */ 260 | function endsWithIgnoreCase($suffix); 261 | function endsWithBytesIgnoreCase($suffix); 262 | function endsWithCodePointsIgnoreCase($suffix); 263 | 264 | 265 | //////////////////////////////////////////////////////////////////////////////// 266 | 267 | 268 | /** 269 | * Removes all whitespace or the specified characters from both sides of this string 270 | * 271 | * @param string $charactersToRemove the characters to remove (optional) 272 | * @param bool $alwaysRemoveWhitespace whether to remove whitespace even if a custom list of characters is provided (optional) 273 | * @return static this instance for chaining 274 | */ 275 | function trim($charactersToRemove = null, $alwaysRemoveWhitespace = null); 276 | 277 | 278 | //////////////////////////////////////////////////////////////////////////////// 279 | 280 | 281 | /** 282 | * Removes all whitespace or the specified characters from the start of this string 283 | * 284 | * @param string $charactersToRemove the characters to remove (optional) 285 | * @param bool $alwaysRemoveWhitespace whether to remove whitespace even if a custom list of characters is provided (optional) 286 | * @return static this instance for chaining 287 | */ 288 | function trimStart($charactersToRemove = null, $alwaysRemoveWhitespace = null); 289 | 290 | 291 | //////////////////////////////////////////////////////////////////////////////// 292 | 293 | 294 | /** 295 | * Removes all whitespace or the specified characters from the end of this string 296 | * 297 | * @param string $charactersToRemove the characters to remove (optional) 298 | * @param bool $alwaysRemoveWhitespace whether to remove whitespace even if a custom list of characters is provided (optional) 299 | * @return static this instance for chaining 300 | */ 301 | function trimEnd($charactersToRemove = null, $alwaysRemoveWhitespace = null); 302 | 303 | 304 | //////////////////////////////////////////////////////////////////////////////// 305 | 306 | 307 | /** 308 | * Returns the first character or the specified number of characters from the start of this string 309 | * 310 | * @param int|null $length the number of characters to return from the start (optional) 311 | * @return static a new instance of this class 312 | */ 313 | function first($length = null); 314 | function firstBytes($length = null); 315 | function firstCodePoints($length = null); 316 | 317 | 318 | //////////////////////////////////////////////////////////////////////////////// 319 | 320 | 321 | /** 322 | * Returns the last character or the specified number of characters from the end of this string 323 | * 324 | * @param int|null $length the number of characters to return from the end (optional) 325 | * @return static a new instance of this class 326 | */ 327 | function last($length = null); 328 | function lastBytes($length = null); 329 | function lastCodePoints($length = null); 330 | 331 | 332 | //////////////////////////////////////////////////////////////////////////////// 333 | 334 | 335 | /** 336 | * Returns the byte at the specified position of this string 337 | * 338 | * @param int $index the zero-based position of the byte to return 339 | * @return string the byte at the specified position 340 | */ 341 | function byteAt($index); 342 | 343 | 344 | //////////////////////////////////////////////////////////////////////////////// 345 | 346 | 347 | /** 348 | * Returns the code point at the specified position of this string 349 | * 350 | * @param int $index the zero-based position of the code point to return 351 | * @return string the code point at the specified position 352 | */ 353 | function codePointAt($index); 354 | 355 | 356 | //////////////////////////////////////////////////////////////////////////////// 357 | 358 | 359 | /** 360 | * Returns whether this string is empty 361 | * 362 | * @return bool 363 | */ 364 | public function isEmpty(); 365 | 366 | 367 | //////////////////////////////////////////////////////////////////////////////// 368 | 369 | 370 | /** 371 | * Returns whether this string consists entirely of ASCII characters 372 | * 373 | * @return bool 374 | */ 375 | public function isAscii(); 376 | 377 | 378 | //////////////////////////////////////////////////////////////////////////////// 379 | 380 | 381 | /** 382 | * Returns whether this string consists entirely of printable ASCII characters 383 | * 384 | * @return bool 385 | */ 386 | public function isPrintableAscii(); 387 | 388 | 389 | //////////////////////////////////////////////////////////////////////////////// 390 | 391 | 392 | /** 393 | * Converts this string to lowercase 394 | * 395 | * @return static this instance for chaining 396 | */ 397 | function toLowerCase(); 398 | function toLowerCaseBytes(); 399 | function toLowerCaseCodePoints(); 400 | 401 | 402 | //////////////////////////////////////////////////////////////////////////////// 403 | 404 | 405 | /** 406 | * Returns whether this string is entirely lowercase 407 | * 408 | * @return bool 409 | */ 410 | function isLowerCase(); 411 | 412 | 413 | //////////////////////////////////////////////////////////////////////////////// 414 | 415 | 416 | /** 417 | * Converts this string to uppercase 418 | * 419 | * @return static this instance for chaining 420 | */ 421 | function toUpperCase(); 422 | function toUpperCaseBytes(); 423 | function toUpperCaseCodePoints(); 424 | 425 | 426 | //////////////////////////////////////////////////////////////////////////////// 427 | 428 | 429 | /** 430 | * Returns whether this string is entirely uppercase 431 | * 432 | * @return bool 433 | */ 434 | function isUpperCase(); 435 | 436 | 437 | //////////////////////////////////////////////////////////////////////////////// 438 | 439 | 440 | /** 441 | * Returns whether this string has its first letter written in uppercase 442 | * 443 | * @return bool 444 | */ 445 | function isCapitalized(); 446 | 447 | 448 | //////////////////////////////////////////////////////////////////////////////// 449 | 450 | 451 | /** 452 | * Truncates this string so that it has at most the specified length 453 | * 454 | * @param int $maxLength the maximum length that this string may have (including any ellipsis) 455 | * @param string|null $ellipsis the string to use as the ellipsis (optional) 456 | * @return static a new instance of this class 457 | */ 458 | function truncate($maxLength, $ellipsis = null); 459 | function truncateBytes($maxLength, $ellipsis = null); 460 | function truncateCodePoints($maxLength, $ellipsis = null); 461 | 462 | 463 | //////////////////////////////////////////////////////////////////////////////// 464 | 465 | 466 | /** 467 | * Truncates this string so that it has at most the specified length 468 | * 469 | * This method tries *not* to break any words whenever possible 470 | * 471 | * @param int $maxLength the maximum length that this string may have (including any ellipsis) 472 | * @param string|null $ellipsis the string to use as the ellipsis (optional) 473 | * @return static a new instance of this class 474 | */ 475 | function truncateSafely($maxLength, $ellipsis = null); 476 | function truncateBytesSafely($maxLength, $ellipsis = null); 477 | function truncateCodePointsSafely($maxLength, $ellipsis = null); 478 | 479 | 480 | //////////////////////////////////////////////////////////////////////////////// 481 | 482 | 483 | /** 484 | * Counts the occurrences of the specified substring in this string 485 | * 486 | * @param string $substring the substring whose occurrences to count 487 | * @return int the number of occurrences 488 | */ 489 | function count($substring = null); 490 | function countBytes($substring = null); 491 | function countCodePoints($substring = null); 492 | 493 | 494 | //////////////////////////////////////////////////////////////////////////////// 495 | 496 | 497 | /** 498 | * Returns the length of this string 499 | * 500 | * @return int the number of characters 501 | */ 502 | function length(); 503 | function lengthInBytes(); 504 | function lengthInCodePoints(); 505 | 506 | 507 | //////////////////////////////////////////////////////////////////////////////// 508 | 509 | 510 | /** 511 | * Removes the specified number of characters from the start of this string 512 | * 513 | * @param int $length the number of characters to remove 514 | * @return static a new instance of this class 515 | */ 516 | function cutStart($length); 517 | function cutBytesAtStart($length); 518 | function cutCodePointsAtStart($length); 519 | 520 | 521 | //////////////////////////////////////////////////////////////////////////////// 522 | 523 | 524 | /** 525 | * Removes the specified number of characters from the end of this string 526 | * 527 | * @param int $length the number of characters to remove 528 | * @return static a new instance of this class 529 | */ 530 | function cutEnd($length); 531 | function cutBytesAtEnd($length); 532 | function cutCodePointsAtEnd($length); 533 | 534 | 535 | //////////////////////////////////////////////////////////////////////////////// 536 | 537 | 538 | /** 539 | * Replaces all occurrences of the specified search string with the given replacement 540 | * 541 | * @param string $searchFor the string to search for 542 | * @param string $replaceWith the string to use as the replacement (optional) 543 | * @return static this instance for chaining 544 | */ 545 | function replace($searchFor, $replaceWith = null); 546 | function replaceBytes($searchFor, $replaceWith = null); 547 | function replaceCodePoints($searchFor, $replaceWith = null); 548 | 549 | 550 | //////////////////////////////////////////////////////////////////////////////// 551 | 552 | 553 | /** 554 | * Replaces all occurrences of the specified search string with the given replacement 555 | * 556 | * This operation is case-insensitive 557 | * 558 | * @param string $searchFor the string to search for 559 | * @param string $replaceWith the string to use as the replacement (optional) 560 | * @return static a new instance of this class 561 | */ 562 | function replaceIgnoreCase($searchFor, $replaceWith = null); 563 | function replaceBytesIgnoreCase($searchFor, $replaceWith = null); 564 | function replaceCodePointsIgnoreCase($searchFor, $replaceWith = null); 565 | 566 | 567 | //////////////////////////////////////////////////////////////////////////////// 568 | 569 | 570 | /** 571 | * Replaces the first occurrence of the specified search string with the given replacement 572 | * 573 | * @param string $searchFor the string to search for 574 | * @param string $replaceWith the string to use as the replacement (optional) 575 | * @return static a new instance of this class 576 | */ 577 | function replaceFirst($searchFor, $replaceWith = null); 578 | function replaceFirstBytes($searchFor, $replaceWith = null); 579 | function replaceFirstCodePoints($searchFor, $replaceWith = null); 580 | 581 | 582 | //////////////////////////////////////////////////////////////////////////////// 583 | 584 | 585 | /** 586 | * Replaces the first occurrence of the specified search string with the given replacement 587 | * 588 | * This operation is case-insensitive 589 | * 590 | * @param string $searchFor the string to search for 591 | * @param string $replaceWith the string to use as the replacement (optional) 592 | * @return static a new instance of this class 593 | */ 594 | function replaceFirstIgnoreCase($searchFor, $replaceWith = null); 595 | function replaceFirstBytesIgnoreCase($searchFor, $replaceWith = null); 596 | function replaceFirstCodePointsIgnoreCase($searchFor, $replaceWith = null); 597 | 598 | 599 | //////////////////////////////////////////////////////////////////////////////// 600 | 601 | 602 | /** 603 | * Replaces the specified part in this string only if it starts with that part 604 | * 605 | * @param string $searchFor the string to search for 606 | * @param string $replaceWith the string to use as the replacement (optional) 607 | * @return static a new instance of this class 608 | */ 609 | function replacePrefix($searchFor, $replaceWith = null); 610 | function replacePrefixBytes($searchFor, $replaceWith = null); 611 | function replacePrefixCodePoints($searchFor, $replaceWith = null); 612 | 613 | 614 | //////////////////////////////////////////////////////////////////////////////// 615 | 616 | 617 | /** 618 | * Replaces the last occurrence of the specified search string with the given replacement 619 | * 620 | * @param string $searchFor the string to search for 621 | * @param string $replaceWith the string to use as the replacement (optional) 622 | * @return static a new instance of this class 623 | */ 624 | function replaceLast($searchFor, $replaceWith = null); 625 | function replaceLastBytes($searchFor, $replaceWith = null); 626 | function replaceLastCodePoints($searchFor, $replaceWith = null); 627 | 628 | 629 | //////////////////////////////////////////////////////////////////////////////// 630 | 631 | 632 | /** 633 | * Replaces the last occurrence of the specified search string with the given replacement 634 | * 635 | * This operation is case-insensitive 636 | * 637 | * @param string $searchFor the string to search for 638 | * @param string $replaceWith the string to use as the replacement (optional) 639 | * @return static a new instance of this class 640 | */ 641 | function replaceLastIgnoreCase($searchFor, $replaceWith = null); 642 | function replaceLastBytesIgnoreCase($searchFor, $replaceWith = null); 643 | function replaceLastCodePointsIgnoreCase($searchFor, $replaceWith = null); 644 | 645 | 646 | //////////////////////////////////////////////////////////////////////////////// 647 | 648 | 649 | /** 650 | * Replaces the specified part in this string only if it ends with that part 651 | * 652 | * @param string $searchFor the string to search for 653 | * @param string $replaceWith the string to use as the replacement (optional) 654 | * @return static a new instance of this class 655 | */ 656 | function replaceSuffix($searchFor, $replaceWith = null); 657 | function replaceSuffixBytes($searchFor, $replaceWith = null); 658 | function replaceSuffixCodePoints($searchFor, $replaceWith = null); 659 | 660 | 661 | //////////////////////////////////////////////////////////////////////////////// 662 | 663 | 664 | /** 665 | * Splits this string into an array of substrings at the specified delimiter 666 | * 667 | * @param string $delimiter the delimiter to split the string at 668 | * @param int|null $limit the maximum number of substrings to return (optional) 669 | * @return static[] an array containing the substrings (which are instances of this class as well) 670 | */ 671 | function split($delimiter, $limit = null); 672 | function splitBytes($delimiter, $limit = null); 673 | function splitCodePoints($delimiter, $limit = null); 674 | 675 | 676 | //////////////////////////////////////////////////////////////////////////////// 677 | 678 | 679 | /** 680 | * Splits this string into an array of substrings at the specified delimiter pattern 681 | * 682 | * @param string $delimiterPattern the regular expression (PCRE) to split the string at 683 | * @param int|null $limit the maximum number of substrings to return (optional) 684 | * @param int|null $flags any combination (bit-wise ORed) of PHP's `PREG_SPLIT_*` flags 685 | * @return static[] an array containing the substrings (which are instances of this class as well) 686 | */ 687 | function splitByRegex($delimiterPattern, $limit = null, $flags = null); 688 | 689 | 690 | //////////////////////////////////////////////////////////////////////////////// 691 | 692 | 693 | /** 694 | * Splits this string into its single words 695 | * 696 | * @param int|null the maximum number of words to return from the start (optional) 697 | * @return static[] the new instances of this class 698 | */ 699 | function words($limit = null); 700 | 701 | 702 | //////////////////////////////////////////////////////////////////////////////// 703 | 704 | 705 | /** 706 | * Returns the part of this string *before* the *first* occurrence of the search string 707 | * 708 | * @param string $search the search string that should delimit the end 709 | * @return static a new instance of this class 710 | */ 711 | function beforeFirst($search); 712 | function beforeFirstBytes($search); 713 | function beforeFirstCodePoints($search); 714 | 715 | 716 | //////////////////////////////////////////////////////////////////////////////// 717 | 718 | 719 | /** 720 | * Returns the part of this string *before* the *last* occurrence of the search string 721 | * 722 | * @param string $search the search string that should delimit the end 723 | * @return static a new instance of this class 724 | */ 725 | function beforeLast($search); 726 | function beforeLastBytes($search); 727 | function beforeLastCodePoints($search); 728 | 729 | 730 | //////////////////////////////////////////////////////////////////////////////// 731 | 732 | 733 | /** 734 | * Returns the part of this string between the two specified substrings 735 | * 736 | * If there are multiple occurrences, the part with the maximum length will be returned 737 | * 738 | * @param string $start the substring whose first occurrence should delimit the start 739 | * @param string $end the substring whose last occurrence should delimit the end 740 | * @return static a new instance of this class 741 | */ 742 | function between($start, $end); 743 | function betweenBytes($start, $end); 744 | function betweenCodePoints($start, $end); 745 | 746 | 747 | //////////////////////////////////////////////////////////////////////////////// 748 | 749 | 750 | /** 751 | * Returns the part of this string *after* the *first* occurrence of the search string 752 | * 753 | * @param string $search the search string that should delimit the start 754 | * @return static a new instance of this class 755 | */ 756 | function afterFirst($search); 757 | function afterFirstBytes($search); 758 | function afterFirstCodePoints($search); 759 | 760 | 761 | //////////////////////////////////////////////////////////////////////////////// 762 | 763 | 764 | /** 765 | * Returns the part of this string *after* the *last* occurrence of the search string 766 | * 767 | * @param string $search the search string that should delimit the start 768 | * @return static a new instance of this class 769 | */ 770 | function afterLast($search); 771 | function afterLastBytes($search); 772 | function afterLastCodePoints($search); 773 | 774 | 775 | //////////////////////////////////////////////////////////////////////////////// 776 | 777 | 778 | /** 779 | * Matches this string against the specified regular expression (PCRE) 780 | * 781 | * @param string $regex the regular expression (PCRE) to match against 782 | * @param mixed|null $matches the array that should be filled with the matches (optional) 783 | * @param bool|null $returnAll whether to return all matches and not only the first one (optional) 784 | * @return bool whether this string matches the regular expression 785 | */ 786 | function matches($regex, &$matches = null, $returnAll = null); 787 | 788 | 789 | //////////////////////////////////////////////////////////////////////////////// 790 | 791 | 792 | /** 793 | * Returns whether this string matches the other string 794 | * 795 | * @param string $other the other string to compare with 796 | * @return bool whether the two strings are equal 797 | */ 798 | function equals($other); 799 | 800 | 801 | //////////////////////////////////////////////////////////////////////////////// 802 | 803 | 804 | /** 805 | * Returns whether this string matches the other string 806 | * 807 | * This operation is case-sensitive 808 | * 809 | * @param string $other the other string to compare with 810 | * @return bool whether the two strings are equal 811 | */ 812 | function equalsIgnoreCase($other); 813 | 814 | 815 | //////////////////////////////////////////////////////////////////////////////// 816 | 817 | 818 | /** 819 | * Compares this string to another string lexicographically 820 | * 821 | * @param string $other the other string to compare to 822 | * @param bool|null $human whether to use human sorting for numbers (e.g. `2` before `10`) (optional) 823 | * @return int an indication whether this string is less than (< 0), equal (= 0) or greater (> 0) 824 | */ 825 | function compareTo($other, $human = null); 826 | function compareToBytes($other, $human = null); 827 | function compareToCodePoints($other, $human = null); 828 | 829 | 830 | //////////////////////////////////////////////////////////////////////////////// 831 | 832 | 833 | /** 834 | * Compares this string to another string lexicographically 835 | * 836 | * This operation is case-sensitive 837 | * 838 | * @param string $other the other string to compare to 839 | * @param bool|null $human whether to use human sorting for numbers (e.g. `2` before `10`) (optional) 840 | * @return int an indication whether this string is less than (< 0), equal (= 0) or greater (> 0) 841 | */ 842 | function compareToIgnoreCase($other, $human = null); 843 | function compareToBytesIgnoreCase($other, $human = null); 844 | function compareToCodePointsIgnoreCase($other, $human = null); 845 | 846 | 847 | //////////////////////////////////////////////////////////////////////////////// 848 | 849 | 850 | /** 851 | * Escapes this string for safe use in HTML 852 | * 853 | * @return static this instance for chaining 854 | */ 855 | function escapeForHtml(); 856 | 857 | 858 | //////////////////////////////////////////////////////////////////////////////// 859 | 860 | 861 | /** 862 | * Normalizes all line endings in this string by using a single unified newline sequence (which may be specified manually) 863 | * 864 | * @param string|null $newlineSequence the target newline sequence to use (optional) 865 | * @return static this instance for chaining 866 | */ 867 | function normalizeLineEndings($newlineSequence = null); 868 | 869 | 870 | //////////////////////////////////////////////////////////////////////////////// 871 | 872 | 873 | /** 874 | * Reverses this string 875 | * 876 | * @return static a new instance of this class 877 | */ 878 | function reverse(); 879 | 880 | 881 | //////////////////////////////////////////////////////////////////////////////// 882 | 883 | 884 | /** 885 | * Turns this string into an acronym (abbreviation) 886 | * 887 | * @param bool|null $excludeLowerCase whether to exclude lowercase letters from the result (optional) 888 | * @return static a new instance of this class 889 | */ 890 | function acronym($excludeLowerCase = null); 891 | ``` 892 | 893 | ### Checking the length of a string 894 | 895 | ```php 896 | $length = count($strInstance); 897 | // or 898 | $length = $strInstance->length(); 899 | // or 900 | $length = $strInstance->count(); 901 | ``` 902 | 903 | ### Creating instances from all entries in an array 904 | 905 | ```php 906 | $instances = \Delight\Str\Str::fromArray($arrayOfStrings); 907 | ``` 908 | 909 | ## Contributing 910 | 911 | All contributions are welcome! If you wish to contribute, please create an issue first so that your feature, problem or question can be discussed. 912 | 913 | ## License 914 | 915 | This project is licensed under the terms of the [MIT License](https://opensource.org/licenses/MIT). 916 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delight-im/str", 3 | "description": "Convenient object-oriented operations on strings", 4 | "require": { 5 | "php": ">=5.3.0", 6 | "ext-mbstring": "*" 7 | }, 8 | "type": "library", 9 | "keywords": [ "string", "strings" ], 10 | "homepage": "https://github.com/delight-im/PHP-Str", 11 | "license": "MIT", 12 | "autoload": { 13 | "psr-4": { 14 | "Delight\\Str\\": "src/" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "0dbd67fb4425bb6893e1471b6c5fd361", 8 | "content-hash": "cc00ceb22fb983689ce569ac334aa397", 9 | "packages": [], 10 | "packages-dev": [], 11 | "aliases": [], 12 | "minimum-stability": "stable", 13 | "stability-flags": [], 14 | "prefer-stable": false, 15 | "prefer-lowest": false, 16 | "platform": { 17 | "php": ">=5.3.0", 18 | "ext-mbstring": "*" 19 | }, 20 | "platform-dev": [] 21 | } 22 | -------------------------------------------------------------------------------- /src/Str.php: -------------------------------------------------------------------------------- 1 | rawString = (string) $rawString; 30 | $this->charset = (isset($charset) ? $charset : self::CHARSET_DEFAULT); 31 | } 32 | 33 | /** 34 | * Static alternative to the constructor for easier chaining 35 | * 36 | * @param string $rawString the string to create an instance from 37 | * @param string|null $charset (optional) the charset to use (one of the values listed by `mb_list_encodings`) 38 | * @return static the new instance 39 | */ 40 | public static function from($rawString, $charset = null) { 41 | return new static($rawString, $charset); 42 | } 43 | 44 | /** 45 | * Variant of the static "constructor" that operates on arrays 46 | * 47 | * @param string[] $rawArray the array of strings to create instances from 48 | * @param string|null $charset (optional) the charset to use (one of the values listed by `mb_list_encodings`) 49 | * @return static[] the new instances of this class 50 | */ 51 | public static function fromArray($rawArray, $charset = null) { 52 | $output = []; 53 | 54 | foreach ($rawArray as $rawEntry) { 55 | $output[] = new static($rawEntry, $charset); 56 | } 57 | 58 | return $output; 59 | } 60 | 61 | /** 62 | * Returns whether this string starts with the supplied other string based on bytes 63 | * 64 | * This operation is case-sensitive 65 | * 66 | * The empty string is not considered to be a part of any other string 67 | * 68 | * @param string $prefix the other string to search for 69 | * @return bool whether the supplied other string can be found at the beginning of this string 70 | */ 71 | public function startsWithBytes($prefix) { 72 | if (\PHP_VERSION_ID >= 80000) { 73 | return $prefix !== '' && \str_starts_with($this->rawString, $prefix); 74 | } 75 | 76 | return $prefix !== '' && \strncmp($this->rawString, $prefix, \strlen($prefix)) === 0; 77 | } 78 | 79 | /** 80 | * Returns whether this string starts with the supplied other string based on code points 81 | * 82 | * This operation is case-sensitive 83 | * 84 | * The empty string is not considered to be a part of any other string 85 | * 86 | * @param string $prefix the other string to search for 87 | * @return bool whether the supplied other string can be found at the beginning of this string 88 | */ 89 | public function startsWithCodePoints($prefix) { 90 | return $this->startsWithBytes($prefix); 91 | } 92 | 93 | /** 94 | * Alias of `startsWithCodePoints` 95 | * 96 | * @param string $prefix 97 | * @return bool 98 | */ 99 | public function startsWith($prefix) { 100 | return $this->startsWithCodePoints($prefix); 101 | } 102 | 103 | /** 104 | * Returns whether this string starts with the supplied other string based on bytes 105 | * 106 | * This operation is case-insensitive 107 | * 108 | * The empty string is not considered to be a part of any other string 109 | * 110 | * @param string $prefix the other string to search for 111 | * @return bool whether the supplied other string can be found at the beginning of this string 112 | */ 113 | public function startsWithBytesIgnoreCase($prefix) { 114 | return $prefix !== '' && \strncasecmp($this->rawString, $prefix, \strlen($prefix)) === 0; 115 | } 116 | 117 | /** 118 | * Returns whether this string starts with the supplied other string based on code points 119 | * 120 | * This operation is case-insensitive 121 | * 122 | * The empty string is not considered to be a part of any other string 123 | * 124 | * @param string $prefix the other string to search for 125 | * @return bool whether the supplied other string can be found at the beginning of this string 126 | */ 127 | public function startsWithCodePointsIgnoreCase($prefix) { 128 | return $prefix !== '' && \mb_stripos($this->rawString, $prefix, 0, $this->charset) === 0; 129 | } 130 | 131 | /** 132 | * Alias of `startsWithBytesIgnoreCase` 133 | * 134 | * @param string $prefix 135 | * @return bool 136 | */ 137 | public function startsWithIgnoreCase($prefix) { 138 | return $this->startsWithBytesIgnoreCase($prefix); 139 | } 140 | 141 | /** 142 | * Returns whether this string contains the supplied other string based on bytes 143 | * 144 | * This operation is case-sensitive 145 | * 146 | * The empty string is not considered to be a part of any other string 147 | * 148 | * @param string $infix the other string to search for 149 | * @return bool whether the supplied other string is contained in this string 150 | */ 151 | public function containsBytes($infix) { 152 | if (\PHP_VERSION_ID >= 80000) { 153 | return $infix !== '' && \str_contains($this->rawString, $infix); 154 | } 155 | 156 | return $infix !== '' && \strpos($this->rawString, $infix, 0) !== false; 157 | } 158 | 159 | /** 160 | * Returns whether this string contains the supplied other string based on code points 161 | * 162 | * This operation is case-sensitive 163 | * 164 | * The empty string is not considered to be a part of any other string 165 | * 166 | * @param string $infix the other string to search for 167 | * @return bool whether the supplied other string is contained in this string 168 | */ 169 | public function containsCodePoints($infix) { 170 | return $this->containsBytes($infix); 171 | } 172 | 173 | /** 174 | * Alias of `containsCodePoints` 175 | * 176 | * @param string $infix 177 | * @return bool 178 | */ 179 | public function contains($infix) { 180 | return $this->containsCodePoints($infix); 181 | } 182 | 183 | /** 184 | * Returns whether this string contains the supplied other string based on bytes 185 | * 186 | * This operation is case-insensitive 187 | * 188 | * The empty string is not considered to be a part of any other string 189 | * 190 | * @param string $infix the other string to search for 191 | * @return bool whether the supplied other string is contained in this string 192 | */ 193 | public function containsBytesIgnoreCase($infix) { 194 | return $infix !== '' && \stripos($this->rawString, $infix, 0) !== false; 195 | } 196 | 197 | /** 198 | * Returns whether this string contains the supplied other string based on code points 199 | * 200 | * This operation is case-insensitive 201 | * 202 | * The empty string is not considered to be a part of any other string 203 | * 204 | * @param string $infix the other string to search for 205 | * @return bool whether the supplied other string is contained in this string 206 | */ 207 | public function containsCodePointsIgnoreCase($infix) { 208 | return $infix !== '' && \mb_stripos($this->rawString, $infix, 0, $this->charset) !== false; 209 | } 210 | 211 | /** 212 | * Alias of `containsCodePointsIgnoreCase` 213 | * 214 | * @param string $infix 215 | * @return bool 216 | */ 217 | public function containsIgnoreCase($infix) { 218 | return $this->containsCodePointsIgnoreCase($infix); 219 | } 220 | 221 | /** 222 | * Returns whether this string ends with the supplied other string based on bytes 223 | * 224 | * This operation is case-sensitive 225 | * 226 | * The empty string is not considered to be a part of any other string 227 | * 228 | * @param string $suffix the other string to search for 229 | * @return bool whether the supplied other string can be found at the end of this string 230 | */ 231 | public function endsWithBytes($suffix) { 232 | if (\PHP_VERSION_ID >= 80000) { 233 | return $suffix !== '' && \str_ends_with($this->rawString, $suffix); 234 | } 235 | 236 | $suffixLength = \strlen($suffix); 237 | 238 | return $suffix !== '' && \substr_compare($this->rawString, $suffix, -$suffixLength, $suffixLength, false) === 0; 239 | } 240 | 241 | /** 242 | * Returns whether this string ends with the supplied other string based on code points 243 | * 244 | * This operation is case-sensitive 245 | * 246 | * The empty string is not considered to be a part of any other string 247 | * 248 | * @param string $suffix the other string to search for 249 | * @return bool whether the supplied other string can be found at the end of this string 250 | */ 251 | public function endsWithCodePoints($suffix) { 252 | return $this->endsWithBytes($suffix); 253 | } 254 | 255 | /** 256 | * Alias of `endsWithCodePoints` 257 | * 258 | * @param string $suffix 259 | * @return bool 260 | */ 261 | public function endsWith($suffix) { 262 | return $this->endsWithCodePoints($suffix); 263 | } 264 | 265 | /** 266 | * Returns whether this string ends with the supplied other string based on bytes 267 | * 268 | * This operation is case-insensitive 269 | * 270 | * The empty string is not considered to be a part of any other string 271 | * 272 | * @param string $suffix the other string to search for 273 | * @return bool whether the supplied other string can be found at the end of this string 274 | */ 275 | public function endsWithBytesIgnoreCase($suffix) { 276 | $suffixLength = \strlen($suffix); 277 | 278 | return $suffix !== '' && \substr_compare($this->rawString, $suffix, -$suffixLength, $suffixLength, true) === 0; 279 | } 280 | 281 | /** 282 | * Returns whether this string ends with the supplied other string based on code points 283 | * 284 | * This operation is case-insensitive 285 | * 286 | * The empty string is not considered to be a part of any other string 287 | * 288 | * @param string $suffix the other string to search for 289 | * @return bool whether the supplied other string can be found at the end of this string 290 | */ 291 | public function endsWithCodePointsIgnoreCase($suffix) { 292 | return $suffix !== '' && \mb_strripos($this->rawString, $suffix, \mb_strlen($this->rawString) - \mb_strlen($suffix), $this->charset) !== false; 293 | } 294 | 295 | /** 296 | * Alias of `endsWithBytesIgnoreCase` 297 | * 298 | * @param string $suffix 299 | * @return bool 300 | */ 301 | public function endsWithIgnoreCase($suffix) { 302 | return $this->endsWithBytesIgnoreCase($suffix); 303 | } 304 | 305 | /** 306 | * Removes all whitespace or the specified characters from both sides of this string 307 | * 308 | * @param string|null $charactersToRemove (optional) the characters to remove 309 | * @param bool|null $alwaysRemoveWhitespace (optional) whether to remove whitespace even if a custom list of characters is provided 310 | * @return static a new instance of this class 311 | */ 312 | public function trim($charactersToRemove = null, $alwaysRemoveWhitespace = null) { 313 | return $this->trimInternal(true, true, $charactersToRemove, $alwaysRemoveWhitespace); 314 | } 315 | 316 | /** 317 | * Removes all whitespace or the specified characters from the start of this string 318 | * 319 | * @param string|null $charactersToRemove (optional) the characters to remove 320 | * @param bool|null $alwaysRemoveWhitespace (optional) whether to remove whitespace even if a custom list of characters is provided 321 | * @return static a new instance of this class 322 | */ 323 | public function trimStart($charactersToRemove = null, $alwaysRemoveWhitespace = null) { 324 | return $this->trimInternal(true, false, $charactersToRemove, $alwaysRemoveWhitespace); 325 | } 326 | 327 | /** 328 | * Removes all whitespace or the specified characters from the end of this string 329 | * 330 | * @param string|null $charactersToRemove (optional) the characters to remove 331 | * @param bool|null $alwaysRemoveWhitespace (optional) whether to remove whitespace even if a custom list of characters is provided 332 | * @return static a new instance of this class 333 | */ 334 | public function trimEnd($charactersToRemove = null, $alwaysRemoveWhitespace = null) { 335 | return $this->trimInternal(false, true, $charactersToRemove, $alwaysRemoveWhitespace); 336 | } 337 | 338 | /** 339 | * Alias of `first` 340 | * 341 | * @param int|null $length 342 | * @return static 343 | * @deprecated use `first` instead 344 | */ 345 | public function start($length = null) { 346 | return $this->first($length); 347 | } 348 | 349 | /** 350 | * Returns the first byte or the specified number of bytes from the start of this string 351 | * 352 | * @param int|null $length (optional) the number of bytes to return from the start 353 | * @return static a new instance of this class 354 | */ 355 | public function firstBytes($length = null) { 356 | if ($length === null) { 357 | $length = 1; 358 | } 359 | 360 | $rawString = \substr($this->rawString, 0, $length); 361 | 362 | return new static($rawString, $this->charset); 363 | } 364 | 365 | /** 366 | * Returns the first code point or the specified number of code points from the start of this string 367 | * 368 | * @param int|null $length (optional) the number of code points to return from the start 369 | * @return static a new instance of this class 370 | */ 371 | public function firstCodePoints($length = null) { 372 | if ($length === null) { 373 | $length = 1; 374 | } 375 | 376 | $rawString = \mb_substr($this->rawString, 0, $length, $this->charset); 377 | 378 | return new static($rawString, $this->charset); 379 | } 380 | 381 | /** 382 | * Alias of `firstCodePoints` 383 | * 384 | * @param int|null $length 385 | * @return static 386 | */ 387 | public function first($length = null) { 388 | return $this->firstCodePoints($length); 389 | } 390 | 391 | /** 392 | * Alias of `last` 393 | * 394 | * @param int|null $length 395 | * @return static 396 | * @deprecated use `last` instead 397 | */ 398 | public function end($length = null) { 399 | return $this->last($length); 400 | } 401 | 402 | /** 403 | * Returns the last byte or the specified number of bytes from the end of this string 404 | * 405 | * @param int|null $length (optional) the number of bytes to return from the end 406 | * @return static a new instance of this class 407 | */ 408 | public function lastBytes($length = null) { 409 | if ($length === null) { 410 | $length = 1; 411 | } 412 | 413 | $offset = $this->lengthInBytes() - $length; 414 | $rawString = \substr($this->rawString, $offset); 415 | 416 | return new static($rawString, $this->charset); 417 | } 418 | 419 | /** 420 | * Returns the last code point or the specified number of code points from the end of this string 421 | * 422 | * @param int|null $length (optional) the number of code points to return from the end 423 | * @return static a new instance of this class 424 | */ 425 | public function lastCodePoints($length = null) { 426 | if ($length === null) { 427 | $length = 1; 428 | } 429 | 430 | $offset = $this->lengthInCodePoints() - $length; 431 | $rawString = \mb_substr($this->rawString, $offset, null, $this->charset); 432 | 433 | return new static($rawString, $this->charset); 434 | } 435 | 436 | /** 437 | * Alias of `lastCodePoints` 438 | * 439 | * @param int|null $length 440 | * @return static 441 | */ 442 | public function last($length = null) { 443 | return $this->lastCodePoints($length); 444 | } 445 | 446 | /** 447 | * Returns the byte at the specified position of this string 448 | * 449 | * @param int $index the zero-based position of the byte to return 450 | * @return string the byte at the specified position 451 | */ 452 | public function byteAt($index) { 453 | return isset($this->rawString[$index]) ? $this->rawString[$index] : ''; 454 | } 455 | 456 | /** 457 | * Returns the code point at the specified position of this string 458 | * 459 | * @param int $index the zero-based position of the code point to return 460 | * @return string the code point at the specified position 461 | */ 462 | public function codePointAt($index) { 463 | return \mb_substr($this->rawString, $index, 1, $this->charset); 464 | } 465 | 466 | /** 467 | * Returns whether this string is empty 468 | * 469 | * @return bool 470 | */ 471 | public function isEmpty() { 472 | return $this->rawString === ''; 473 | } 474 | 475 | /** 476 | * Returns whether this string consists entirely of ASCII characters 477 | * 478 | * @return bool 479 | */ 480 | public function isAscii() { 481 | return \preg_match('/[^\x00-\x7F]/', $this->rawString) === 0; 482 | } 483 | 484 | /** 485 | * Returns whether this string consists entirely of printable ASCII characters 486 | * 487 | * @return bool 488 | */ 489 | public function isPrintableAscii() { 490 | return \preg_match('/[^\x20-\x7E]/', $this->rawString) === 0; 491 | } 492 | 493 | /** 494 | * Converts this string to lowercase based on bytes 495 | * 496 | * @return static a new instance of this class 497 | */ 498 | public function toLowerCaseBytes() { 499 | $rawString = \strtolower($this->rawString); 500 | 501 | return new static($rawString, $this->charset); 502 | } 503 | 504 | /** 505 | * Converts this string to lowercase based on code points 506 | * 507 | * @return static a new instance of this class 508 | */ 509 | public function toLowerCaseCodePoints() { 510 | $rawString = \mb_strtolower($this->rawString, $this->charset); 511 | 512 | return new static($rawString, $this->charset); 513 | } 514 | 515 | /** 516 | * Alias of `toLowerCaseCodePoints` 517 | * 518 | * @return static 519 | */ 520 | public function toLowerCase() { 521 | return $this->toLowerCaseCodePoints(); 522 | } 523 | 524 | /** 525 | * Returns whether this string is entirely lowercase 526 | * 527 | * @return bool 528 | * @deprecated use `equals` and `toLowerCase` instead 529 | */ 530 | public function isLowerCase() { 531 | return $this->equals($this->toLowerCase()); 532 | } 533 | 534 | /** 535 | * Converts this string to uppercase based on bytes 536 | * 537 | * @return static a new instance of this class 538 | */ 539 | public function toUpperCaseBytes() { 540 | $rawString = \strtoupper($this->rawString); 541 | 542 | return new static($rawString, $this->charset); 543 | } 544 | 545 | /** 546 | * Converts this string to uppercase based on code points 547 | * 548 | * @return static a new instance of this class 549 | */ 550 | public function toUpperCaseCodePoints() { 551 | $rawString = \mb_strtoupper($this->rawString, $this->charset); 552 | 553 | return new static($rawString, $this->charset); 554 | } 555 | 556 | /** 557 | * Alias of `toUpperCaseCodePoints` 558 | * 559 | * @return static 560 | */ 561 | public function toUpperCase() { 562 | return $this->toUpperCaseCodePoints(); 563 | } 564 | 565 | /** 566 | * Returns whether this string is entirely uppercase 567 | * 568 | * @return bool 569 | * @deprecated use `equals` and `toUpperCase` instead 570 | */ 571 | public function isUpperCase() { 572 | return $this->equals($this->toUpperCase()); 573 | } 574 | 575 | /** 576 | * Returns whether this string has its first letter written in uppercase 577 | * 578 | * @return bool 579 | * @deprecated use `first` and `equals` and `toUpperCase` instead 580 | */ 581 | public function isCapitalized() { 582 | return $this->first()->isUpperCase(); 583 | } 584 | 585 | /** 586 | * Truncates this string so that it has at most the specified length in bytes 587 | * 588 | * @param int $maxLength the maximum length that this string may have (including any ellipsis) 589 | * @param string|null $ellipsis (optional) the string to use as the ellipsis 590 | * @return static a new instance of this class 591 | */ 592 | public function truncateBytes($maxLength, $ellipsis = null) { 593 | return $this->truncateInternal(true, false, $maxLength, $ellipsis, false); 594 | } 595 | 596 | /** 597 | * Truncates this string so that it has at most the specified length in code points 598 | * 599 | * @param int $maxLength the maximum length that this string may have (including any ellipsis) 600 | * @param string|null $ellipsis (optional) the string to use as the ellipsis 601 | * @return static a new instance of this class 602 | */ 603 | public function truncateCodePoints($maxLength, $ellipsis = null) { 604 | return $this->truncateInternal(false, true, $maxLength, $ellipsis, false); 605 | } 606 | 607 | /** 608 | * Alias of `truncateCodePoints` 609 | * 610 | * @param int $maxLength 611 | * @param string|null $ellipsis 612 | * @return static 613 | */ 614 | public function truncate($maxLength, $ellipsis = null) { 615 | return $this->truncateCodePoints($maxLength, $ellipsis); 616 | } 617 | 618 | /** 619 | * Truncates this string so that it has at most the specified length in bytes 620 | * 621 | * This method tries *not* to break any words whenever possible 622 | * 623 | * @param int $maxLength the maximum length that this string may have (including any ellipsis) 624 | * @param string|null $ellipsis (optional) the string to use as the ellipsis 625 | * @return static a new instance of this class 626 | */ 627 | public function truncateBytesSafely($maxLength, $ellipsis = null) { 628 | return $this->truncateInternal(true, false, $maxLength, $ellipsis, true); 629 | } 630 | 631 | /** 632 | * Truncates this string so that it has at most the specified length in code points 633 | * 634 | * This method tries *not* to break any words whenever possible 635 | * 636 | * @param int $maxLength the maximum length that this string may have (including any ellipsis) 637 | * @param string|null $ellipsis (optional) the string to use as the ellipsis 638 | * @return static a new instance of this class 639 | */ 640 | public function truncateCodePointsSafely($maxLength, $ellipsis = null) { 641 | return $this->truncateInternal(false, true, $maxLength, $ellipsis, true); 642 | } 643 | 644 | /** 645 | * Alias of `truncateCodePointsSafely` 646 | * 647 | * @param int $maxLength 648 | * @param string|null $ellipsis 649 | * @return static 650 | */ 651 | public function truncateSafely($maxLength, $ellipsis = null) { 652 | return $this->truncateCodePointsSafely($maxLength, $ellipsis); 653 | } 654 | 655 | /** 656 | * Counts the occurrences of the specified substring in this string based on bytes 657 | * 658 | * This operation is case-sensitive 659 | * 660 | * The empty string is not considered to be a part of any other string 661 | * 662 | * @param string|null $substring (optional) the substring whose occurrences to count 663 | * @return int the number of occurrences 664 | */ 665 | public function countBytes($substring = null) { 666 | if ($substring === null) { 667 | return \strlen($this->rawString); 668 | } 669 | else { 670 | if ($substring === '') { 671 | return 0; 672 | } 673 | 674 | return \substr_count($this->rawString, $substring); 675 | } 676 | } 677 | 678 | /** 679 | * Counts the occurrences of the specified substring in this string based on code points 680 | * 681 | * This operation is case-sensitive 682 | * 683 | * The empty string is not considered to be a part of any other string 684 | * 685 | * @param string|null $substring (optional) the substring whose occurrences to count 686 | * @return int the number of occurrences 687 | */ 688 | public function countCodePoints($substring = null) { 689 | if ($substring === null) { 690 | return \mb_strlen($this->rawString, $this->charset); 691 | } 692 | else { 693 | if ($substring === '') { 694 | return 0; 695 | } 696 | 697 | return \mb_substr_count($this->rawString, $substring, $this->charset); 698 | } 699 | } 700 | 701 | /** 702 | * Alias of `countCodePoints` 703 | * 704 | * @param string|null $substring 705 | * @return int 706 | */ 707 | public function count($substring = null) { 708 | return $this->countCodePoints($substring); 709 | } 710 | 711 | /** 712 | * Returns the length of this string in bytes 713 | * 714 | * @return int the number of bytes 715 | */ 716 | public function lengthInBytes() { 717 | return \strlen($this->rawString); 718 | } 719 | 720 | /** 721 | * Returns the length of this string in code points 722 | * 723 | * @return int the number of code points 724 | */ 725 | public function lengthInCodePoints() { 726 | return $this->countCodePoints(); 727 | } 728 | 729 | /** 730 | * Alias of `lengthInCodePoints` 731 | * 732 | * @return int 733 | */ 734 | public function length() { 735 | return $this->lengthInCodePoints(); 736 | } 737 | 738 | /** 739 | * Removes the specified number of bytes from the start of this string 740 | * 741 | * @param int $length the number of bytes to remove 742 | * @return static a new instance of this class 743 | */ 744 | public function cutBytesAtStart($length) { 745 | $rawString = \substr($this->rawString, $length); 746 | 747 | return new static($rawString, $this->charset); 748 | } 749 | 750 | /** 751 | * Removes the specified number of code points from the start of this string 752 | * 753 | * @param int $length the number of code points to remove 754 | * @return static a new instance of this class 755 | */ 756 | public function cutCodePointsAtStart($length) { 757 | $rawString = \mb_substr($this->rawString, $length, null, $this->charset); 758 | 759 | return new static($rawString, $this->charset); 760 | } 761 | 762 | /** 763 | * Alias of `cutCodePointsAtStart` 764 | * 765 | * @param int $length 766 | * @return static 767 | */ 768 | public function cutStart($length) { 769 | return $this->cutCodePointsAtStart($length); 770 | } 771 | 772 | /** 773 | * Removes the specified number of bytes from the end of this string 774 | * 775 | * @param int $length the number of bytes to remove 776 | * @return static a new instance of this class 777 | */ 778 | public function cutBytesAtEnd($length) { 779 | $rawString = \substr($this->rawString, 0, $this->lengthInBytes() - $length); 780 | 781 | return new static($rawString, $this->charset); 782 | } 783 | 784 | /** 785 | * Removes the specified number of code points from the end of this string 786 | * 787 | * @param int $length the number of code points to remove 788 | * @return static a new instance of this class 789 | */ 790 | public function cutCodePointsAtEnd($length) { 791 | $rawString = \mb_substr($this->rawString, 0, $this->lengthInCodePoints() - $length, $this->charset); 792 | 793 | return new static($rawString, $this->charset); 794 | } 795 | 796 | /** 797 | * Alias of `cutCodePointsAtEnd` 798 | * 799 | * @param int $length 800 | * @return static 801 | */ 802 | public function cutEnd($length) { 803 | return $this->cutCodePointsAtEnd($length); 804 | } 805 | 806 | /** 807 | * Replaces all occurrences of the specified search string with the given replacement based on bytes 808 | * 809 | * This operation is case-sensitive 810 | * 811 | * The empty string is not considered to be a part of any other string 812 | * 813 | * @param string $searchFor the string to search for 814 | * @param string|null $replaceWith (optional) the string to use as the replacement 815 | * @return static a new instance of this class 816 | */ 817 | public function replaceBytes($searchFor, $replaceWith = null) { 818 | return $this->replaceInternal('str_replace', $searchFor, $replaceWith); 819 | } 820 | 821 | /** 822 | * Replaces all occurrences of the specified search string with the given replacement based on code points 823 | * 824 | * This operation is case-sensitive 825 | * 826 | * The empty string is not considered to be a part of any other string 827 | * 828 | * @param string $searchFor the string to search for 829 | * @param string|null $replaceWith (optional) the string to use as the replacement 830 | * @return static a new instance of this class 831 | */ 832 | public function replaceCodePoints($searchFor, $replaceWith = null) { 833 | return $this->replaceBytes($searchFor, $replaceWith); 834 | } 835 | 836 | /** 837 | * Alias of `replaceCodePoints` 838 | * 839 | * @param string $searchFor 840 | * @param string|null $replaceWith 841 | * @return static 842 | */ 843 | public function replace($searchFor, $replaceWith = null) { 844 | return $this->replaceCodePoints($searchFor, $replaceWith); 845 | } 846 | 847 | /** 848 | * Replaces all occurrences of the specified search string with the given replacement based on bytes 849 | * 850 | * This operation is case-insensitive 851 | * 852 | * The empty string is not considered to be a part of any other string 853 | * 854 | * @param string $searchFor the string to search for 855 | * @param string|null $replaceWith (optional) the string to use as the replacement 856 | * @return static a new instance of this class 857 | */ 858 | public function replaceBytesIgnoreCase($searchFor, $replaceWith = null) { 859 | return $this->replaceInternal('str_ireplace', $searchFor, $replaceWith); 860 | } 861 | 862 | /** 863 | * Replaces all occurrences of the specified search string with the given replacement based on code points 864 | * 865 | * This operation is case-insensitive 866 | * 867 | * The empty string is not considered to be a part of any other string 868 | * 869 | * @param string $searchFor the string to search for 870 | * @param string|null $replaceWith (optional) the string to use as the replacement 871 | * @return static a new instance of this class 872 | */ 873 | public function replaceCodePointsIgnoreCase($searchFor, $replaceWith = null) { 874 | $searchFor = (string) $searchFor; 875 | 876 | if ($searchFor === '') { 877 | return $this; 878 | } 879 | 880 | $replaceWith = ($replaceWith === null) ? '' : (string) $replaceWith; 881 | $regexPattern = '/' . \preg_quote($searchFor, '/') . '/ui'; 882 | $segments = \preg_split($regexPattern, $this->rawString, -1); 883 | $newRawString = \implode($replaceWith, $segments); 884 | 885 | return new static($newRawString, $this->charset); 886 | } 887 | 888 | /** 889 | * Alias of `replaceBytesIgnoreCase` 890 | * 891 | * @param string $searchFor 892 | * @param string|null $replaceWith 893 | * @return static 894 | */ 895 | public function replaceIgnoreCase($searchFor, $replaceWith = null) { 896 | return $this->replaceBytesIgnoreCase($searchFor, $replaceWith); 897 | } 898 | 899 | /** 900 | * Replaces the first occurrence of the specified search string with the given replacement based on bytes 901 | * 902 | * This operation is case-sensitive 903 | * 904 | * The empty string is not considered to be a part of any other string 905 | * 906 | * @param string $searchFor the string to search for 907 | * @param string|null $replaceWith (optional) the string to use as the replacement 908 | * @return static a new instance of this class 909 | */ 910 | public function replaceFirstBytes($searchFor, $replaceWith = null) { 911 | return $this->replaceOneInternal(true, false, 'strpos', $searchFor, $replaceWith); 912 | } 913 | 914 | /** 915 | * Replaces the first occurrence of the specified search string with the given replacement based on code points 916 | * 917 | * This operation is case-sensitive 918 | * 919 | * The empty string is not considered to be a part of any other string 920 | * 921 | * @param string $searchFor the string to search for 922 | * @param string|null $replaceWith (optional) the string to use as the replacement 923 | * @return static a new instance of this class 924 | */ 925 | public function replaceFirstCodePoints($searchFor, $replaceWith = null) { 926 | return $this->replaceOneInternal(false, true, 'mb_strpos', $searchFor, $replaceWith); 927 | } 928 | 929 | /** 930 | * Alias of `replaceFirstCodePoints` 931 | * 932 | * @param string $searchFor 933 | * @param string|null $replaceWith 934 | * @return static 935 | */ 936 | public function replaceFirst($searchFor, $replaceWith = null) { 937 | return $this->replaceFirstCodePoints($searchFor, $replaceWith); 938 | } 939 | 940 | /** 941 | * Replaces the first occurrence of the specified search string with the given replacement based on bytes 942 | * 943 | * This operation is case-insensitive 944 | * 945 | * The empty string is not considered to be a part of any other string 946 | * 947 | * @param string $searchFor the string to search for 948 | * @param string|null $replaceWith (optional) the string to use as the replacement 949 | * @return static a new instance of this class 950 | */ 951 | public function replaceFirstBytesIgnoreCase($searchFor, $replaceWith = null) { 952 | return $this->replaceOneInternal(true, false, 'stripos', $searchFor, $replaceWith); 953 | } 954 | 955 | /** 956 | * Replaces the first occurrence of the specified search string with the given replacement based on code points 957 | * 958 | * This operation is case-insensitive 959 | * 960 | * The empty string is not considered to be a part of any other string 961 | * 962 | * @param string $searchFor the string to search for 963 | * @param string|null $replaceWith (optional) the string to use as the replacement 964 | * @return static a new instance of this class 965 | */ 966 | public function replaceFirstCodePointsIgnoreCase($searchFor, $replaceWith = null) { 967 | return $this->replaceOneInternal(false, true, 'mb_stripos', $searchFor, $replaceWith); 968 | } 969 | 970 | /** 971 | * Alias of `replaceFirstCodePointsIgnoreCase` 972 | * 973 | * @param string $searchFor 974 | * @param string|null $replaceWith 975 | * @return static 976 | */ 977 | public function replaceFirstIgnoreCase($searchFor, $replaceWith = null) { 978 | return $this->replaceFirstCodePointsIgnoreCase($searchFor, $replaceWith); 979 | } 980 | 981 | /** 982 | * Replaces the specified part in this string only if it starts with that part based on bytes 983 | * 984 | * This operation is case-sensitive 985 | * 986 | * The empty string is not considered to be a part of any other string 987 | * 988 | * @param string $searchFor the string to search for 989 | * @param string|null $replaceWith (optional) the string to use as the replacement 990 | * @return static a new instance of this class 991 | */ 992 | public function replacePrefixBytes($searchFor, $replaceWith = null) { 993 | if ($this->startsWithBytes($searchFor)) { 994 | return $this->replaceFirstBytes($searchFor, $replaceWith); 995 | } 996 | else { 997 | return $this; 998 | } 999 | } 1000 | 1001 | /** 1002 | * Replaces the specified part in this string only if it starts with that part based on code points 1003 | * 1004 | * This operation is case-sensitive 1005 | * 1006 | * The empty string is not considered to be a part of any other string 1007 | * 1008 | * @param string $searchFor the string to search for 1009 | * @param string|null $replaceWith (optional) the string to use as the replacement 1010 | * @return static a new instance of this class 1011 | */ 1012 | public function replacePrefixCodePoints($searchFor, $replaceWith = null) { 1013 | if ($this->startsWithCodePoints($searchFor)) { 1014 | return $this->replaceFirstCodePoints($searchFor, $replaceWith); 1015 | } 1016 | else { 1017 | return $this; 1018 | } 1019 | } 1020 | 1021 | /** 1022 | * Alias of `replacePrefixCodePoints` 1023 | * 1024 | * @param string $searchFor 1025 | * @param string|null $replaceWith 1026 | * @return static 1027 | */ 1028 | public function replacePrefix($searchFor, $replaceWith = null) { 1029 | return $this->replacePrefixCodePoints($searchFor, $replaceWith); 1030 | } 1031 | 1032 | /** 1033 | * Replaces the last occurrence of the specified search string with the given replacement based on bytes 1034 | * 1035 | * This operation is case-sensitive 1036 | * 1037 | * The empty string is not considered to be a part of any other string 1038 | * 1039 | * @param string $searchFor the string to search for 1040 | * @param string|null $replaceWith (optional) the string to use as the replacement 1041 | * @return static a new instance of this class 1042 | */ 1043 | public function replaceLastBytes($searchFor, $replaceWith = null) { 1044 | return $this->replaceOneInternal(true, false, 'strrpos', $searchFor, $replaceWith); 1045 | } 1046 | 1047 | /** 1048 | * Replaces the last occurrence of the specified search string with the given replacement based on code points 1049 | * 1050 | * This operation is case-sensitive 1051 | * 1052 | * The empty string is not considered to be a part of any other string 1053 | * 1054 | * @param string $searchFor the string to search for 1055 | * @param string|null $replaceWith (optional) the string to use as the replacement 1056 | * @return static a new instance of this class 1057 | */ 1058 | public function replaceLastCodePoints($searchFor, $replaceWith = null) { 1059 | return $this->replaceOneInternal(false, true, 'mb_strrpos', $searchFor, $replaceWith); 1060 | } 1061 | 1062 | /** 1063 | * Alias of `replaceLastCodePoints` 1064 | * 1065 | * @param string $searchFor 1066 | * @param string|null $replaceWith 1067 | * @return static 1068 | */ 1069 | public function replaceLast($searchFor, $replaceWith = null) { 1070 | return $this->replaceLastCodePoints($searchFor, $replaceWith); 1071 | } 1072 | 1073 | /** 1074 | * Replaces the last occurrence of the specified search string with the given replacement based on bytes 1075 | * 1076 | * This operation is case-insensitive 1077 | * 1078 | * The empty string is not considered to be a part of any other string 1079 | * 1080 | * @param string $searchFor the string to search for 1081 | * @param string|null $replaceWith (optional) the string to use as the replacement 1082 | * @return static a new instance of this class 1083 | */ 1084 | public function replaceLastBytesIgnoreCase($searchFor, $replaceWith = null) { 1085 | return $this->replaceOneInternal(true, false, 'strripos', $searchFor, $replaceWith); 1086 | } 1087 | 1088 | /** 1089 | * Replaces the last occurrence of the specified search string with the given replacement based on code points 1090 | * 1091 | * This operation is case-insensitive 1092 | * 1093 | * The empty string is not considered to be a part of any other string 1094 | * 1095 | * @param string $searchFor the string to search for 1096 | * @param string|null $replaceWith (optional) the string to use as the replacement 1097 | * @return static a new instance of this class 1098 | */ 1099 | public function replaceLastCodePointsIgnoreCase($searchFor, $replaceWith = null) { 1100 | return $this->replaceOneInternal(false, true, 'mb_strripos', $searchFor, $replaceWith); 1101 | } 1102 | 1103 | /** 1104 | * Alias of `replaceLastCodePointsIgnoreCase` 1105 | * 1106 | * @param string $searchFor 1107 | * @param string|null $replaceWith 1108 | * @return static 1109 | */ 1110 | public function replaceLastIgnoreCase($searchFor, $replaceWith = null) { 1111 | return $this->replaceLastCodePointsIgnoreCase($searchFor, $replaceWith); 1112 | } 1113 | 1114 | /** 1115 | * Replaces the specified part in this string only if it ends with that part based on bytes 1116 | * 1117 | * This operation is case-sensitive 1118 | * 1119 | * The empty string is not considered to be a part of any other string 1120 | * 1121 | * @param string $searchFor the string to search for 1122 | * @param string|null $replaceWith (optional) the string to use as the replacement 1123 | * @return static a new instance of this class 1124 | */ 1125 | public function replaceSuffixBytes($searchFor, $replaceWith = null) { 1126 | if ($this->endsWithBytes($searchFor)) { 1127 | return $this->replaceLastBytes($searchFor, $replaceWith); 1128 | } 1129 | else { 1130 | return $this; 1131 | } 1132 | } 1133 | 1134 | /** 1135 | * Replaces the specified part in this string only if it ends with that part based on code points 1136 | * 1137 | * This operation is case-sensitive 1138 | * 1139 | * The empty string is not considered to be a part of any other string 1140 | * 1141 | * @param string $searchFor the string to search for 1142 | * @param string|null $replaceWith (optional) the string to use as the replacement 1143 | * @return static a new instance of this class 1144 | */ 1145 | public function replaceSuffixCodePoints($searchFor, $replaceWith = null) { 1146 | if ($this->endsWithCodePoints($searchFor)) { 1147 | return $this->replaceLastCodePoints($searchFor, $replaceWith); 1148 | } 1149 | else { 1150 | return $this; 1151 | } 1152 | } 1153 | 1154 | /** 1155 | * Alias of `replaceSuffixCodePoints` 1156 | * 1157 | * @param string $searchFor 1158 | * @param string|null $replaceWith 1159 | * @return static 1160 | */ 1161 | public function replaceSuffix($searchFor, $replaceWith = null) { 1162 | return $this->replaceSuffixCodePoints($searchFor, $replaceWith); 1163 | } 1164 | 1165 | /** 1166 | * Splits this string into an array of substrings at the specified delimiter based on bytes 1167 | * 1168 | * This operation is case-sensitive 1169 | * 1170 | * The empty string is not considered to be a part of any other string 1171 | * 1172 | * @param string $delimiter the delimiter to split the string at 1173 | * @param int|null $limit (optional) the maximum number of substrings to return 1174 | * @return static[] the new instances of this class 1175 | */ 1176 | public function splitBytes($delimiter, $limit = null) { 1177 | if ($delimiter === '') { 1178 | return [ $this ]; 1179 | } 1180 | 1181 | if ($limit === null) { 1182 | $limit = \PHP_INT_MAX; 1183 | } 1184 | 1185 | return self::fromArray(\explode($delimiter, $this->rawString, $limit)); 1186 | } 1187 | 1188 | /** 1189 | * Splits this string into an array of substrings at the specified delimiter based on code points 1190 | * 1191 | * This operation is case-sensitive 1192 | * 1193 | * The empty string is not considered to be a part of any other string 1194 | * 1195 | * @param string $delimiter the delimiter to split the string at 1196 | * @param int|null $limit (optional) the maximum number of substrings to return 1197 | * @return static[] the new instances of this class 1198 | */ 1199 | public function splitCodePoints($delimiter, $limit = null) { 1200 | return $this->splitBytes($delimiter, $limit); 1201 | } 1202 | 1203 | /** 1204 | * Alias of `splitCodePoints` 1205 | * 1206 | * @param string $delimiter 1207 | * @param int|null $limit 1208 | * @return static[] 1209 | */ 1210 | public function split($delimiter, $limit = null) { 1211 | return $this->splitCodePoints($delimiter, $limit); 1212 | } 1213 | 1214 | /** 1215 | * Splits this string into an array of substrings at the specified delimiter pattern 1216 | * 1217 | * @param string $delimiterPattern the regular expression (PCRE) to split the string at 1218 | * @param int|null $limit (optional) the maximum number of substrings to return 1219 | * @param int|null $flags (optional) any combination (bit-wise ORed) of PHP's `PREG_SPLIT_*` flags 1220 | * @return static[] the new instances of this class 1221 | */ 1222 | public function splitByRegex($delimiterPattern, $limit = null, $flags = null) { 1223 | if ($limit === null) { 1224 | $limit = -1; 1225 | } 1226 | 1227 | if ($flags === null) { 1228 | $flags = 0; 1229 | } 1230 | 1231 | return self::fromArray(\preg_split($delimiterPattern, $this->rawString, $limit, $flags)); 1232 | } 1233 | 1234 | /** 1235 | * Splits this string into its single words 1236 | * 1237 | * @param int|null $limit (optional) the maximum number of words to return from the start 1238 | * @return static[] the new instances of this class 1239 | */ 1240 | public function words($limit = null) { 1241 | // if a limit has been specified 1242 | if ($limit !== null) { 1243 | // get one entry more than requested 1244 | $limit += 1; 1245 | } 1246 | 1247 | // split the string into words 1248 | $words = $this->splitByRegex('/(?![' . "\u{0027}\u{2019}" . '])[\\s\\p{P}]+/u', $limit, \PREG_SPLIT_NO_EMPTY); 1249 | 1250 | // if a limit has been specified 1251 | if ($limit !== null) { 1252 | // discard the last entry (which contains the remainder of the string) 1253 | \array_pop($words); 1254 | } 1255 | 1256 | // return the words 1257 | return $words; 1258 | } 1259 | 1260 | /** 1261 | * Returns the part of this string *before* the *first* occurrence of the search string based on bytes 1262 | * 1263 | * This operation is case-sensitive 1264 | * 1265 | * The empty string (as a search string) is not considered to be a part of any other string 1266 | * 1267 | * If the given search string is not found anywhere, an empty string is returned 1268 | * 1269 | * @param string $search the search string that should delimit the end 1270 | * @return static a new instance of this class 1271 | */ 1272 | public function beforeFirstBytes($search) { 1273 | return self::sideInternal($this->rawString, $this->charset, true, false, 'strpos', $search, -1); 1274 | } 1275 | 1276 | /** 1277 | * Returns the part of this string *before* the *first* occurrence of the search string based on code points 1278 | * 1279 | * This operation is case-sensitive 1280 | * 1281 | * The empty string (as a search string) is not considered to be a part of any other string 1282 | * 1283 | * If the given search string is not found anywhere, an empty string is returned 1284 | * 1285 | * @param string $search the search string that should delimit the end 1286 | * @return static a new instance of this class 1287 | */ 1288 | public function beforeFirstCodePoints($search) { 1289 | return self::sideInternal($this->rawString, $this->charset, false, true, 'mb_strpos', $search, -1); 1290 | } 1291 | 1292 | /** 1293 | * Alias of `beforeFirstCodePoints` 1294 | * 1295 | * @param string $search 1296 | * @return static 1297 | */ 1298 | public function beforeFirst($search) { 1299 | return $this->beforeFirstCodePoints($search); 1300 | } 1301 | 1302 | /** 1303 | * Returns the part of this string *before* the *last* occurrence of the search string based on bytes 1304 | * 1305 | * This operation is case-sensitive 1306 | * 1307 | * The empty string (as a search string) is not considered to be a part of any other string 1308 | * 1309 | * If the given search string is not found anywhere, an empty string is returned 1310 | * 1311 | * @param string $search the search string that should delimit the end 1312 | * @return static a new instance of this class 1313 | */ 1314 | public function beforeLastBytes($search) { 1315 | return self::sideInternal($this->rawString, $this->charset, true, false, 'strrpos', $search, -1); 1316 | } 1317 | 1318 | /** 1319 | * Returns the part of this string *before* the *last* occurrence of the search string based on code points 1320 | * 1321 | * This operation is case-sensitive 1322 | * 1323 | * The empty string (as a search string) is not considered to be a part of any other string 1324 | * 1325 | * If the given search string is not found anywhere, an empty string is returned 1326 | * 1327 | * @param string $search the search string that should delimit the end 1328 | * @return static a new instance of this class 1329 | */ 1330 | public function beforeLastCodePoints($search) { 1331 | return self::sideInternal($this->rawString, $this->charset, false, true, 'mb_strrpos', $search, -1); 1332 | } 1333 | 1334 | /** 1335 | * Alias of `beforeLastCodePoints` 1336 | * 1337 | * @param string $search 1338 | * @return static 1339 | */ 1340 | public function beforeLast($search) { 1341 | return $this->beforeLastCodePoints($search); 1342 | } 1343 | 1344 | /** 1345 | * Returns the part of this string between the two specified substrings based on bytes 1346 | * 1347 | * This operation is case-sensitive 1348 | * 1349 | * If there are multiple occurrences, the part with the maximum length will be returned 1350 | * 1351 | * The empty string (as a search string) is not considered to be a part of any other string 1352 | * 1353 | * If one of the given search strings is not found anywhere, an empty string is returned 1354 | * 1355 | * @param string $start the substring whose first occurrence should delimit the start 1356 | * @param string $end the substring whose last occurrence should delimit the end 1357 | * @return static a new instance of this class 1358 | */ 1359 | public function betweenBytes($start, $end) { 1360 | $afterStart = self::sideInternal($this->rawString, $this->charset, true, false, 'strpos', $start, 1); 1361 | 1362 | return self::sideInternal($afterStart, $this->charset, true, false, 'strrpos', $end, -1); 1363 | } 1364 | 1365 | /** 1366 | * Returns the part of this string between the two specified substrings based on code points 1367 | * 1368 | * This operation is case-sensitive 1369 | * 1370 | * If there are multiple occurrences, the part with the maximum length will be returned 1371 | * 1372 | * The empty string (as a search string) is not considered to be a part of any other string 1373 | * 1374 | * If one of the given search strings is not found anywhere, an empty string is returned 1375 | * 1376 | * @param string $start the substring whose first occurrence should delimit the start 1377 | * @param string $end the substring whose last occurrence should delimit the end 1378 | * @return static a new instance of this class 1379 | */ 1380 | public function betweenCodePoints($start, $end) { 1381 | $afterStart = self::sideInternal($this->rawString, $this->charset, false, true, 'mb_strpos', $start, 1); 1382 | 1383 | return self::sideInternal($afterStart, $this->charset, false, true, 'mb_strrpos', $end, -1); 1384 | } 1385 | 1386 | /** 1387 | * Alias of `betweenCodePoints` 1388 | * 1389 | * @param string $start 1390 | * @param string $end 1391 | * @return static 1392 | */ 1393 | public function between($start, $end) { 1394 | return $this->betweenCodePoints($start, $end); 1395 | } 1396 | 1397 | /** 1398 | * Returns the part of this string *after* the *first* occurrence of the search string based on bytes 1399 | * 1400 | * This operation is case-sensitive 1401 | * 1402 | * The empty string (as a search string) is not considered to be a part of any other string 1403 | * 1404 | * If the given search string is not found anywhere, an empty string is returned 1405 | * 1406 | * @param string $search the search string that should delimit the start 1407 | * @return static a new instance of this class 1408 | */ 1409 | public function afterFirstBytes($search) { 1410 | return self::sideInternal($this->rawString, $this->charset, true, false, 'strpos', $search, 1); 1411 | } 1412 | 1413 | /** 1414 | * Returns the part of this string *after* the *first* occurrence of the search string based on code points 1415 | * 1416 | * This operation is case-sensitive 1417 | * 1418 | * The empty string (as a search string) is not considered to be a part of any other string 1419 | * 1420 | * If the given search string is not found anywhere, an empty string is returned 1421 | * 1422 | * @param string $search the search string that should delimit the start 1423 | * @return static a new instance of this class 1424 | */ 1425 | public function afterFirstCodePoints($search) { 1426 | return self::sideInternal($this->rawString, $this->charset, false, true, 'mb_strpos', $search, 1); 1427 | } 1428 | 1429 | /** 1430 | * Alias of `afterFirstCodePoints` 1431 | * 1432 | * @param string $search 1433 | * @return static 1434 | */ 1435 | public function afterFirst($search) { 1436 | return $this->afterFirstCodePoints($search); 1437 | } 1438 | 1439 | /** 1440 | * Returns the part of this string *after* the *last* occurrence of the search string based on bytes 1441 | * 1442 | * This operation is case-sensitive 1443 | * 1444 | * The empty string (as a search string) is not considered to be a part of any other string 1445 | * 1446 | * If the given search string is not found anywhere, an empty string is returned 1447 | * 1448 | * @param string $search the search string that should delimit the start 1449 | * @return static a new instance of this class 1450 | */ 1451 | public function afterLastBytes($search) { 1452 | return self::sideInternal($this->rawString, $this->charset, true, false, 'strrpos', $search, 1); 1453 | } 1454 | 1455 | /** 1456 | * Returns the part of this string *after* the *last* occurrence of the search string based on code points 1457 | * 1458 | * This operation is case-sensitive 1459 | * 1460 | * The empty string (as a search string) is not considered to be a part of any other string 1461 | * 1462 | * If the given search string is not found anywhere, an empty string is returned 1463 | * 1464 | * @param string $search the search string that should delimit the start 1465 | * @return static a new instance of this class 1466 | */ 1467 | public function afterLastCodePoints($search) { 1468 | return self::sideInternal($this->rawString, $this->charset, false, true, 'mb_strrpos', $search, 1); 1469 | } 1470 | 1471 | /** 1472 | * Alias of `afterLastCodePoints` 1473 | * 1474 | * @param string $search 1475 | * @return static 1476 | */ 1477 | public function afterLast($search) { 1478 | return $this->afterLastCodePoints($search); 1479 | } 1480 | 1481 | /** 1482 | * Matches this string against the specified regular expression (PCRE) 1483 | * 1484 | * @param string $regex the regular expression (PCRE) to match against 1485 | * @param mixed|null $matches (optional) the array that should be filled with the matches 1486 | * @param bool|null $returnAll (optional) whether to return all matches and not only the first one 1487 | * @return bool whether this string matches the regular expression 1488 | */ 1489 | public function matches($regex, &$matches = null, $returnAll = null) { 1490 | if ($returnAll) { 1491 | return \preg_match_all($regex, $this->rawString, $matches) > 0; 1492 | } 1493 | else { 1494 | return \preg_match($regex, $this->rawString, $matches) === 1; 1495 | } 1496 | } 1497 | 1498 | /** 1499 | * Returns whether this string matches the other string 1500 | * 1501 | * This operation is case-sensitive 1502 | * 1503 | * @param string $other the other string to compare with 1504 | * @return bool whether the two strings are equal 1505 | */ 1506 | public function equals($other) { 1507 | return $this->compareTo($other) === 0; 1508 | } 1509 | 1510 | /** 1511 | * Returns whether this string matches the other string 1512 | * 1513 | * This operation is case-insensitive 1514 | * 1515 | * @param string $other the other string to compare with 1516 | * @return bool whether the two strings are equal 1517 | */ 1518 | public function equalsIgnoreCase($other) { 1519 | return $this->compareToIgnoreCase($other) === 0; 1520 | } 1521 | 1522 | /** 1523 | * Compares this string to another string lexicographically based on bytes 1524 | * 1525 | * This operation is case-sensitive 1526 | * 1527 | * @param string $other the other string to compare to 1528 | * @param bool|null $human (optional) whether to use human sorting for numbers (e.g. `2` before `10`) 1529 | * @return int an indication whether this string is less than (< 0), equal (= 0) or greater (> 0) 1530 | */ 1531 | public function compareToBytes($other, $human = null) { 1532 | if ($human) { 1533 | return \strnatcmp($this->rawString, $other); 1534 | } 1535 | else { 1536 | return \strcmp($this->rawString, $other); 1537 | } 1538 | } 1539 | 1540 | /** 1541 | * Compares this string to another string lexicographically based on code points 1542 | * 1543 | * This operation is case-sensitive 1544 | * 1545 | * @param string $other the other string to compare to 1546 | * @param bool|null $human (optional) whether to use human sorting for numbers (e.g. `2` before `10`) 1547 | * @return int an indication whether this string is less than (< 0), equal (= 0) or greater (> 0) 1548 | */ 1549 | public function compareToCodePoints($other, $human = null) { 1550 | return $this->compareToBytes($other, $human); 1551 | } 1552 | 1553 | /** 1554 | * Alias of `compareToCodePoints` 1555 | * 1556 | * @param string $other 1557 | * @param bool|null $human 1558 | * @return int 1559 | */ 1560 | public function compareTo($other, $human = null) { 1561 | return $this->compareToCodePoints($other, $human); 1562 | } 1563 | 1564 | /** 1565 | * Compares this string to another string lexicographically based on bytes 1566 | * 1567 | * This operation is case-insensitive 1568 | * 1569 | * @param string $other the other string to compare to 1570 | * @param bool|null $human (optional) whether to use human sorting for numbers (e.g. `2` before `10`) 1571 | * @return int an indication whether this string is less than (< 0), equal (= 0) or greater (> 0) 1572 | */ 1573 | public function compareToBytesIgnoreCase($other, $human = null) { 1574 | if ($human) { 1575 | return \strnatcasecmp($this->rawString, $other); 1576 | } 1577 | else { 1578 | return \strcasecmp($this->rawString, $other); 1579 | } 1580 | } 1581 | 1582 | /** 1583 | * Compares this string to another string lexicographically based on code points 1584 | * 1585 | * This operation is case-insensitive 1586 | * 1587 | * @param string $other the other string to compare to 1588 | * @param bool|null $human (optional) whether to use human sorting for numbers (e.g. `2` before `10`) 1589 | * @return int an indication whether this string is less than (< 0), equal (= 0) or greater (> 0) 1590 | */ 1591 | public function compareToCodePointsIgnoreCase($other, $human = null) { 1592 | $a = \mb_strtolower($this->rawString, $this->charset); 1593 | $b = \mb_strtolower($other, $this->charset); 1594 | 1595 | if ($human) { 1596 | return \strnatcmp($a, $b); 1597 | } 1598 | else { 1599 | return \strcmp($a, $b); 1600 | } 1601 | } 1602 | 1603 | /** 1604 | * Alias of `compareToBytesIgnoreCase` 1605 | * 1606 | * @param string $other 1607 | * @param bool|null $human 1608 | * @return int 1609 | */ 1610 | public function compareToIgnoreCase($other, $human = null) { 1611 | return $this->compareToBytesIgnoreCase($other, $human); 1612 | } 1613 | 1614 | /** 1615 | * Escapes this string for safe use in HTML 1616 | * 1617 | * @return static a new instance of this class 1618 | */ 1619 | public function escapeForHtml() { 1620 | $rawString = \htmlspecialchars($this->rawString, \ENT_QUOTES, $this->charset); 1621 | 1622 | return new static($rawString, $this->charset); 1623 | } 1624 | 1625 | /** 1626 | * Normalizes all line endings in this string by using a single unified newline sequence (which may be specified manually) 1627 | * 1628 | * @param string|null $newlineSequence (optional) the target newline sequence to use 1629 | * @return static a new instance of this class 1630 | */ 1631 | public function normalizeLineEndings($newlineSequence = null) { 1632 | if ($newlineSequence === null) { 1633 | $newlineSequence = "\n"; 1634 | } 1635 | 1636 | $rawString = \preg_replace('/\R/u', $newlineSequence, $this->rawString); 1637 | 1638 | return new static($rawString, $this->charset); 1639 | } 1640 | 1641 | /** 1642 | * Reverses this string 1643 | * 1644 | * @return static a new instance of this class 1645 | */ 1646 | public function reverse() { 1647 | if (\preg_match_all('/./us', $this->rawString, $matches)) { 1648 | $rawString = \join('', \array_reverse($matches[0])); 1649 | 1650 | return new static($rawString, $this->charset); 1651 | } 1652 | else { 1653 | return $this; 1654 | } 1655 | } 1656 | 1657 | /** 1658 | * Turns this string into an acronym (abbreviation) 1659 | * 1660 | * @param bool|null $excludeLowerCase (optional) whether to exclude lowercase letters from the result 1661 | * @return static a new instance of this class 1662 | * @deprecated 1663 | */ 1664 | public function acronym($excludeLowerCase = null) { 1665 | $words = $this->words(); 1666 | 1667 | $rawString = ''; 1668 | 1669 | foreach ($words as $word) { 1670 | if (!$excludeLowerCase || $word->isCapitalized()) { 1671 | $rawString .= $word->first(); 1672 | } 1673 | } 1674 | 1675 | return new static($rawString, $this->charset); 1676 | } 1677 | 1678 | public function __toString() { 1679 | return $this->rawString; 1680 | } 1681 | 1682 | private function trimInternal($trimAtStart, $trimAtEnd, $charactersToRemove = null, $alwaysRemoveWhitespace = null) { 1683 | $defaultCharactersToRemove = " \t\n\r\0\x0B"; 1684 | 1685 | if ($charactersToRemove === null) { 1686 | $charactersToRemove = $defaultCharactersToRemove; 1687 | } 1688 | 1689 | if ($alwaysRemoveWhitespace === null) { 1690 | $alwaysRemoveWhitespace = false; 1691 | } 1692 | 1693 | if ($alwaysRemoveWhitespace) { 1694 | $charactersToRemove .= $defaultCharactersToRemove; 1695 | } 1696 | 1697 | // if non-ASCII (or multi-byte UTF-8) characters are to be removed 1698 | if (\preg_match('/[^\x00-\x7F]/', $charactersToRemove) === 1) { 1699 | $charactersToRemoveRegexClass = '[' . \preg_quote($charactersToRemove, '/') . ']'; 1700 | 1701 | if ($trimAtStart) { 1702 | if ($trimAtEnd) { 1703 | $charactersToRemoveRegexPattern = '/^' . $charactersToRemoveRegexClass . '+|' . $charactersToRemoveRegexClass . '+$/uD'; 1704 | } 1705 | else { 1706 | $charactersToRemoveRegexPattern = '/^' . $charactersToRemoveRegexClass . '+/uD'; 1707 | } 1708 | } 1709 | elseif ($trimAtEnd) { 1710 | $charactersToRemoveRegexPattern = '/' . $charactersToRemoveRegexClass . '+$/uD'; 1711 | } 1712 | else { 1713 | throw new \Exception("Either 'trimAtStart' or 'trimAtEnd' must be 'true'"); 1714 | } 1715 | 1716 | $newRawString = \preg_replace($charactersToRemoveRegexPattern, '', $this->rawString, -1); 1717 | } 1718 | // if only ASCII (or single-byte UTF-8) characters are to be removed 1719 | else { 1720 | if ($trimAtStart) { 1721 | if ($trimAtEnd) { 1722 | $trimFunc = 'trim'; 1723 | } 1724 | else { 1725 | $trimFunc = 'ltrim'; 1726 | } 1727 | } 1728 | elseif ($trimAtEnd) { 1729 | $trimFunc = 'rtrim'; 1730 | } 1731 | else { 1732 | throw new \Exception("Either 'trimAtStart' or 'trimAtEnd' must be 'true'"); 1733 | } 1734 | 1735 | $newRawString = $trimFunc($this->rawString, $charactersToRemove); 1736 | } 1737 | 1738 | return new static($newRawString, $this->charset); 1739 | } 1740 | 1741 | private function truncateInternal($operateOnBytes, $operateOnCodePoints, $maxLength, $ellipsis, $safe) { 1742 | if ($operateOnBytes) { 1743 | $previousLength = $this->lengthInBytes(); 1744 | } 1745 | elseif ($operateOnCodePoints) { 1746 | $previousLength = $this->lengthInCodePoints(); 1747 | } 1748 | else { 1749 | throw new \Exception("Either 'operateOnBytes' or 'operateOnCodePoints' must be 'true'"); 1750 | } 1751 | 1752 | // if the string doesn't actually need to be truncated for the desired maximum length 1753 | if ($previousLength <= $maxLength) { 1754 | // return it unchanged 1755 | return $this; 1756 | } 1757 | // if the string does indeed need to be truncated 1758 | else { 1759 | // if no ellipsis string has been specified 1760 | if ($ellipsis === null) { 1761 | // assume three dots as the default 1762 | $ellipsis = '...'; 1763 | } 1764 | 1765 | // calculate the actual maximum length without the ellipsis 1766 | if ($operateOnBytes) { 1767 | $maxLength -= \strlen($ellipsis); 1768 | } 1769 | elseif ($operateOnCodePoints) { 1770 | $maxLength -= \mb_strlen($ellipsis, $this->charset); 1771 | } 1772 | else { 1773 | throw new \Exception("Either 'operateOnBytes' or 'operateOnCodePoints' must be 'true'"); 1774 | } 1775 | 1776 | // if not even the ellipsis string fits into a string of the specified maximum length 1777 | if ($maxLength < 0) { 1778 | return ''; 1779 | } 1780 | 1781 | // truncate the string to the desired length 1782 | if ($operateOnBytes) { 1783 | $rawString = \substr($this->rawString, 0, $maxLength); 1784 | } 1785 | elseif ($operateOnCodePoints) { 1786 | $rawString = \mb_substr($this->rawString, 0, $maxLength, $this->charset); 1787 | } 1788 | else { 1789 | throw new \Exception("Either 'operateOnBytes' or 'operateOnCodePoints' must be 'true'"); 1790 | } 1791 | 1792 | // if we don't want to break words 1793 | if ($safe) { 1794 | if ($operateOnBytes) { 1795 | $boundaryChars = \substr($this->rawString, $maxLength - 1, 2); 1796 | } 1797 | elseif ($operateOnCodePoints) { 1798 | $boundaryChars = \mb_substr($this->rawString, $maxLength - 1, 2, $this->charset); 1799 | } 1800 | else { 1801 | throw new \Exception("Either 'operateOnBytes' or 'operateOnCodePoints' must be 'true'"); 1802 | } 1803 | 1804 | // if the truncated string *does* end *within* a word 1805 | if (!\preg_match('/\\W/u', $boundaryChars)) { 1806 | // if there's some word boundary before 1807 | if (\preg_match('/.*\\W/u', $rawString, $matches)) { 1808 | // truncate there instead 1809 | $rawString = $matches[0]; 1810 | } 1811 | } 1812 | } 1813 | 1814 | // return the correctly truncated string together with the ellipsis 1815 | return new static($rawString . $ellipsis, $this->charset); 1816 | } 1817 | } 1818 | 1819 | private function replaceInternal(callable $func, $searchFor, $replaceWith) { 1820 | if ($replaceWith === null) { 1821 | $replaceWith = ''; 1822 | } 1823 | 1824 | $rawString = $func($searchFor, $replaceWith, $this->rawString); 1825 | 1826 | return new static($rawString, $this->charset); 1827 | } 1828 | 1829 | private function replaceOneInternal($operateOnBytes, $operateOnCodePoints, callable $func, $searchFor, $replaceWith) { 1830 | if ($searchFor === '') { 1831 | return $this; 1832 | } 1833 | 1834 | if ($operateOnBytes) { 1835 | $pos = $func($this->rawString, $searchFor, 0); 1836 | } 1837 | elseif ($operateOnCodePoints) { 1838 | $pos = $func($this->rawString, $searchFor, 0, $this->charset); 1839 | } 1840 | else { 1841 | throw new \Exception("Either 'operateOnBytes' or 'operateOnCodePoints' must be 'true'"); 1842 | } 1843 | 1844 | if ($pos === false) { 1845 | return $this; 1846 | } 1847 | else { 1848 | if ($replaceWith === null) { 1849 | $replaceWith = ''; 1850 | } 1851 | 1852 | if ($operateOnBytes) { 1853 | $prefix = \substr($this->rawString, 0, $pos); 1854 | $searchForLength = \strlen($searchFor); 1855 | $suffix = \substr($this->rawString, $pos + $searchForLength); 1856 | } 1857 | elseif ($operateOnCodePoints) { 1858 | $prefix = \mb_substr($this->rawString, 0, $pos, $this->charset); 1859 | $searchForLength = \mb_strlen($searchFor, $this->charset); 1860 | $suffix = \mb_substr($this->rawString, $pos + $searchForLength, null, $this->charset); 1861 | } 1862 | else { 1863 | throw new \Exception("Either 'operateOnBytes' or 'operateOnCodePoints' must be 'true'"); 1864 | } 1865 | 1866 | return new static($prefix . $replaceWith . $suffix, $this->charset); 1867 | } 1868 | } 1869 | 1870 | private static function sideInternal($subject, $encoding, $operateOnBytes, $operateOnCodePoints, callable $strposFunc, $delimiter, $direction) { 1871 | if ($delimiter === '') { 1872 | return new static('', $encoding); 1873 | } 1874 | 1875 | if ($operateOnBytes) { 1876 | $delimiterStartPos = $strposFunc($subject, $delimiter, 0); 1877 | } 1878 | elseif ($operateOnCodePoints) { 1879 | $delimiterStartPos = $strposFunc($subject, $delimiter, 0, $encoding); 1880 | } 1881 | else { 1882 | throw new \Exception("Either 'operateOnBytes' or 'operateOnCodePoints' must be 'true'"); 1883 | } 1884 | 1885 | if ($delimiterStartPos === false) { 1886 | return new static('', $encoding); 1887 | } 1888 | 1889 | if ($direction === -1) { 1890 | $offset = 0; 1891 | $length = $delimiterStartPos; 1892 | } 1893 | else { 1894 | if ($operateOnBytes) { 1895 | $offset = $delimiterStartPos + \strlen($delimiter); 1896 | } 1897 | elseif ($operateOnCodePoints) { 1898 | $offset = $delimiterStartPos + \mb_strlen($delimiter, $encoding); 1899 | } 1900 | else { 1901 | throw new \Exception("Either 'operateOnBytes' or 'operateOnCodePoints' must be 'true'"); 1902 | } 1903 | 1904 | $length = null; 1905 | } 1906 | 1907 | if ($operateOnBytes) { 1908 | if ($length !== null) { 1909 | $newRawString = \substr($subject, $offset, $length); 1910 | } 1911 | else { 1912 | $newRawString = \substr($subject, $offset); 1913 | } 1914 | } 1915 | elseif ($operateOnCodePoints) { 1916 | $newRawString = \mb_substr($subject, $offset, $length, $encoding); 1917 | } 1918 | else { 1919 | throw new \Exception("Either 'operateOnBytes' or 'operateOnCodePoints' must be 'true'"); 1920 | } 1921 | 1922 | return new static($newRawString, $encoding); 1923 | } 1924 | 1925 | } 1926 | --------------------------------------------------------------------------------