1 2/** 3 * This file is part of the Phalcon. 4 * 5 * (c) Phalcon Team <team@phalcon.com> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11namespace Phalcon\Helper; 12 13use RuntimeException; 14 15/** 16 * This class offers quick string functions throughout the framework 17 */ 18class Str 19{ 20 const RANDOM_ALNUM = 0; // Only alpha numeric characters [a-zA-Z0-9] 21 const RANDOM_ALPHA = 1; // Only alphabetical characters [azAZ] 22 const RANDOM_DISTINCT = 5; // Only alpha numeric uppercase characters exclude similar sharacters [2345679ACDEFHJKLMNPRSTUVWXYZ] 23 const RANDOM_HEXDEC = 2; // Only hexadecimal characters [0-9a-f] 24 const RANDOM_NOZERO = 4; // Only numbers without 0 [1-9] 25 const RANDOM_NUMERIC = 3; // Only numbers [0-9] 26 27 /** 28 * Converts strings to camelize style 29 * 30 * ```php 31 * use Phalcon\Helper\Str; 32 * 33 * echo Str::camelize("coco_bongo"); // CocoBongo 34 * echo Str::camelize("co_co-bon_go", "-"); // Co_coBon_go 35 * echo Str::camelize("co_co-bon_go", "_-"); // CoCoBonGo 36 * ``` 37 * 38 * @param string $text 39 * @param mixed $delimiter 40 * 41 * @return string 42 */ 43 final public static function camelize(string! text, var delimiter = null) -> string 44 { 45 return text->camelize(delimiter); 46 } 47 48 /** 49 * Concatenates strings using the separator only once without duplication in 50 * places concatenation 51 * 52 * ```php 53 * $str = Phalcon\Helper\Str::concat( 54 * "/", 55 * "/tmp/", 56 * "/folder_1/", 57 * "/folder_2", 58 * "folder_3/" 59 * ); 60 * 61 * echo $str; // /tmp/folder_1/folder_2/folder_3/ 62 * ``` 63 * 64 * @param string separator 65 * @param string a 66 * @param string b 67 * @param string ...N 68 * 69 * @return string 70 */ 71 final public static function concat() -> string 72 { 73 var argument, arguments, data, first, last, prefix, delimiter, suffix; 74 75 let arguments = func_get_args(); 76 77 if unlikely count(arguments) < 3 { 78 throw new Exception("concat needs at least three parameters"); 79 } 80 81 let delimiter = Arr::first(arguments), 82 arguments = Arr::sliceRight(arguments), 83 first = Arr::first(arguments), 84 last = Arr::last(arguments), 85 prefix = "", 86 suffix = "", 87 data = []; 88 89 if self::startsWith(first, delimiter) { 90 let prefix = delimiter; 91 } 92 93 if self::endsWith(last, delimiter) { 94 let suffix = delimiter; 95 } 96 97 98 for argument in arguments { 99 let data[] = trim(argument, delimiter); 100 } 101 102 return prefix . implode(delimiter, data) . suffix; 103 } 104 105 /** 106 * Returns number of vowels in provided string. Uses a regular expression 107 * to count the number of vowels (A, E, I, O, U) in a string. 108 * 109 * @param string $string 110 * 111 * @return int 112 */ 113 final public static function countVowels(string! text) -> int 114 { 115 var matches; 116 117 preg_match_all("/[aeiou]/i", text, matches); 118 119 return count(matches[0]); 120 } 121 122 /** 123 * Decapitalizes the first letter of the string and then adds it with rest 124 * of the string. Omit the upperRest parameter to keep the rest of the 125 * string intact, or set it to true to convert to uppercase. 126 * 127 * @param string $string 128 * @param bool $upperRest 129 * @param string $encoding 130 * 131 * @return string 132 */ 133 final public static function decapitalize( 134 string! text, 135 bool upperRest = false, 136 string! encoding = "UTF-8" 137 ) -> string 138 { 139 var substr, suffix; 140 141 if function_exists("mb_substr") { 142 let substr = mb_substr(text, 1); 143 } else { 144 let substr = substr(text, 1); 145 } 146 147 if upperRest { 148 if function_exists("mb_strtoupper") { 149 let suffix = mb_strtoupper(substr, encoding); 150 } else { 151 let suffix = substr->upper(); 152 } 153 } else { 154 let suffix = substr; 155 } 156 157 if function_exists("mb_strtolower") { 158 return mb_strtolower(mb_substr(text, 0, 1), encoding) . suffix; 159 } else { 160 return strtolower(substr(text, 0, 1)) . suffix; 161 } 162 } 163 164 /** 165 * Removes a number from a string or decrements that number if it already is defined. 166 * defined 167 * 168 * ```php 169 * use Phalcon\Helper\Str; 170 * 171 * echo Str::decrement("a_1"); // "a" 172 * echo Str::decrement("a_2"); // "a_1" 173 * ``` 174 * 175 * @param string $text 176 * @param string $separator 177 * 178 * @return string 179 */ 180 final public static function decrement(string text, string separator = "_") -> string 181 { 182 var parts, number; 183 184 let parts = explode(separator, text); 185 186 if fetch number, parts[1] { 187 let number--; 188 if (number <= 0) { 189 return parts[0]; 190 } 191 } 192 193 return parts[0] . separator. number; 194 } 195 196 /** 197 * Accepts a file name (without extension) and returns a calculated 198 * directory structure with the filename in the end 199 * 200 * ```php 201 * use Phalcon\Helper\Str; 202 * 203 * echo Str::dirFromFile("file1234.jpg"); // fi/le/12/ 204 * ``` 205 * 206 * @param string $file 207 * 208 * @return string 209 */ 210 final public static function dirFromFile(string! file) -> string 211 { 212 var name, start; 213 214 let name = pathinfo(file, PATHINFO_FILENAME), 215 start = substr(name, 0, -2); 216 217 if !empty start { 218 let start = str_replace(".", "-", start); 219 } 220 221 if !start { 222 let start = substr(name, 0, 1); 223 } 224 225 return implode("/", str_split(start, 2)) . "/"; 226 } 227 228 /** 229 * Accepts a directory name and ensures that it ends with 230 * DIRECTORY_SEPARATOR 231 * 232 * ```php 233 * use Phalcon\Helper\Str; 234 * 235 * echo Str::dirSeparator("/home/phalcon"); // /home/phalcon/ 236 * ``` 237 * 238 * @param string $directory 239 * 240 * @return string 241 */ 242 final public static function dirSeparator(string! directory) -> string 243 { 244 return rtrim(directory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; 245 } 246 247 /** 248 * Generates random text in accordance with the template 249 * 250 * ```php 251 * use Phalcon\Helper\Str; 252 * 253 * // Hi my name is a Bob 254 * echo Str::dynamic("{Hi|Hello}, my name is a {Bob|Mark|Jon}!"); 255 * 256 * // Hi my name is a Jon 257 * echo Str::dynamic("{Hi|Hello}, my name is a {Bob|Mark|Jon}!"); 258 * 259 * // Hello my name is a Bob 260 * echo Str::dynamic("{Hi|Hello}, my name is a {Bob|Mark|Jon}!"); 261 * 262 * // Hello my name is a Zyxep 263 * echo Str::dynamic( 264 * "[Hi/Hello], my name is a [Zyxep/Mark]!", 265 * "[", "]", 266 * "/" 267 * ); 268 * ``` 269 * 270 * @param string $text 271 * @param string $leftDelimiter 272 * @param string $rightDelimiter 273 * @param string $separator 274 * 275 * @return string 276 */ 277 final public static function dynamic( 278 string! text, 279 string! leftDelimiter = "{", 280 string! rightDelimiter = "}", 281 string! separator = "|" 282 ) -> string 283 { 284 var ldS, rdS, matches, match, words, word, sub; 285 string pattern; 286 287 if unlikely substr_count(text, leftDelimiter) !== substr_count(text, rightDelimiter) { 288 throw new RuntimeException( 289 "Syntax error in string \"" . text . "\"" 290 ); 291 } 292 293 let ldS = preg_quote(leftDelimiter), 294 rdS = preg_quote(rightDelimiter), 295 pattern = "/" . ldS . "([^" . ldS . rdS . "]+)" . rdS . "/", 296 matches = []; 297 298 if !preg_match_all(pattern, text, matches, 2) { 299 return text; 300 } 301 302 if typeof matches == "array" { 303 for match in matches { 304 if !isset match[0] || !isset match[1] { 305 continue; 306 } 307 308 let words = explode(separator, match[1]), 309 word = words[array_rand(words)], 310 sub = preg_quote(match[0], separator), 311 text = preg_replace("/" . sub . "/", word, text, 1); 312 } 313 } 314 315 return text; 316 } 317 318 /** 319 * Check if a string ends with a given string 320 * 321 * ```php 322 * use Phalcon\Helper\Str; 323 * 324 * echo Str::endsWith("Hello", "llo"); // true 325 * echo Str::endsWith("Hello", "LLO", false); // false 326 * echo Str::endsWith("Hello", "LLO"); // true 327 * ``` 328 * 329 * @param string $text 330 * @param string $end 331 * @param bool $ignoreCase 332 * 333 * @return bool 334 */ 335 final public static function endsWith(string text, string end, bool ignoreCase = true) -> bool 336 { 337 return ends_with(text, end, ignoreCase); 338 } 339 340 /** 341 * Returns the first string there is between the strings from the 342 * parameter start and end. 343 * 344 * @param string $text 345 * @param string $start 346 * @param string $end 347 * 348 * @return string 349 */ 350 final public static function firstBetween( 351 string! text, 352 string! start, 353 string! end 354 ) -> string 355 { 356 if function_exists("mb_strstr") { 357 let text = (string) mb_strstr(mb_strstr(text, start), end, true); 358 } else { 359 let text = (string) strstr(strstr(text, start), end, true); 360 } 361 362 return trim( 363 text, 364 start . end 365 ); 366 } 367 368 /** 369 * Changes a text to a URL friendly one 370 * 371 * @param string $text 372 * @param string $separator 373 * @param bool $lowercase 374 * @param mixed|null $replace 375 * 376 * @return string 377 * @throws Exception 378 */ 379 final public static function friendly( 380 string! text, 381 string! separator = "-", 382 bool lowercase = true, 383 var replace = null 384 ) -> string { 385 var friendly, matrix, search; 386 387 let matrix = [ 388 "Š" : "S", "š" : "s", "Đ" : "Dj", "Ð" : "Dj", 389 "đ" : "dj", "Ž" : "Z", "ž" : "z", "Č" : "C", 390 "č" : "c", "Ć" : "C", "ć" : "c", "À" : "A", 391 "Á" : "A", "Â" : "A", "Ã" : "A", "Ä" : "A", 392 "Å" : "A", "Æ" : "A", "Ç" : "C", "È" : "E", 393 "É" : "E", "Ê" : "E", "Ë" : "E", "Ì" : "I", 394 "Í" : "I", "Î" : "I", "Ï" : "I", "Ñ" : "N", 395 "Ò" : "O", "Ó" : "O", "Ô" : "O", "Õ" : "O", 396 "Ö" : "O", "Ø" : "O", "Ù" : "U", "Ú" : "U", 397 "Û" : "U", "Ü" : "U", "Ý" : "Y", "Þ" : "B", 398 "ß" : "Ss", "à" : "a", "á" : "a", "â" : "a", 399 "ã" : "a", "ä" : "a", "å" : "a", "æ" : "a", 400 "ç" : "c", "è" : "e", "é" : "e", "ê" : "e", 401 "ë" : "e", "ì" : "i", "í" : "i", "î" : "i", 402 "ï" : "i", "ð" : "o", "ñ" : "n", "ò" : "o", 403 "ó" : "o", "ô" : "o", "õ" : "o", "ö" : "o", 404 "ø" : "o", "ù" : "u", "ú" : "u", "û" : "u", 405 "ý" : "y", "ý" : "y", "þ" : "b", "ÿ" : "y", 406 "Ŕ" : "R", "ŕ" : "r", "ē" : "e", "'" : "", 407 "&" : " and ", "\r\n" : " ", "\n" : " " 408 ]; 409 410 if replace { 411 if unlikely (typeof replace != "array" && typeof replace != "string") { 412 throw new Exception( 413 "Parameter replace must be an array or a string" 414 ); 415 } 416 417 if typeof replace !== "array" { 418 let replace = [replace]; 419 } 420 421 for search in replace { 422 let matrix[search] = " "; 423 } 424 } 425 426 let text = str_replace(array_keys(matrix), array_values(matrix), text), 427 friendly = preg_replace( 428 "/[^a-zA-Z0-9\\/_|+ -]/", 429 "", 430 text 431 ); 432 433 if lowercase { 434 let friendly = strtolower(friendly); 435 } 436 437 let friendly = preg_replace("/[\\/_|+ -]+/", separator, friendly), 438 friendly = trim(friendly, separator); 439 440 return friendly; 441 } 442 443 /** 444 * Makes an underscored or dashed phrase human-readable 445 * 446 * ```php 447 * use Phalcon\Helper\Str; 448 * 449 * echo Str::humanize("start-a-horse"); // "start a horse" 450 * echo Str::humanize("five_cats"); // "five cats" 451 * ``` 452 * 453 * @param string $text 454 * 455 * @return string 456 */ 457 final public static function humanize(string! text) -> string 458 { 459 return preg_replace("#[_-]+#", " ", trim(text)); 460 } 461 462 /** 463 * Lets you determine whether or not a string includes another string. 464 * 465 * @param string $needle 466 * @param string $haystack 467 * 468 * @return bool 469 */ 470 final public static function includes(string! needle, string! haystack) -> bool 471 { 472 if function_exists("mb_strpos") { 473 return false !== mb_strpos(haystack, needle); 474 } else { 475 return false !== strpos(haystack, needle); 476 } 477 } 478 479 /** 480 * Adds a number to a string or increment that number if it already is 481 * defined 482 * 483 * ```php 484 * use Phalcon\Helper\Str; 485 * 486 * echo Str::increment("a"); // "a_1" 487 * echo Str::increment("a_1"); // "a_2" 488 * ``` 489 * 490 * @param string $text 491 * @param string $separator 492 * 493 * @return string 494 */ 495 final public static function increment(string text, string separator = "_") -> string 496 { 497 var parts, number; 498 499 let parts = explode(separator, text); 500 501 if fetch number, parts[1] { 502 let number++; 503 } else { 504 let number = 1; 505 } 506 507 return parts[0] . separator. number; 508 } 509 510 /** 511 * Compare two strings and returns true if both strings are anagram, 512 * false otherwise. 513 * 514 * @param string $first 515 * @param string $second 516 * 517 * @return bool 518 */ 519 final public static function isAnagram(string! first, string! second) -> bool 520 { 521 return count_chars(first, 1) === count_chars(second, 1); 522 } 523 524 /** 525 * Returns true if the given string is lower case, false otherwise. 526 * 527 * @param string $text 528 * @param string $encoding 529 * 530 * @return bool 531 */ 532 final public static function isLower(string! text, string! encoding = "UTF-8") -> bool 533 { 534 if function_exists("mb_strtolower") { 535 return text === mb_strtolower(text, encoding); 536 } else { 537 return text === text->lower(); 538 } 539 } 540 541 /** 542 * Returns true if the given string is a palindrome, false otherwise. 543 * 544 * @param string $text 545 * 546 * @return bool 547 */ 548 final public static function isPalindrome(string! text) -> bool 549 { 550 return strrev(text) === text; 551 } 552 553 /** 554 * Returns true if the given string is upper case, false otherwise. 555 * 556 * @param string text 557 * @param string encoding 558 * 559 * @return bool 560 */ 561 final public static function isUpper(string! text, string! encoding = "UTF-8") -> bool 562 { 563 if function_exists("mb_strtoupper") { 564 return text === mb_strtoupper(text, encoding); 565 } else { 566 return text === text->upper(); 567 } 568 } 569 570 /** 571 * Lowercases a string, this function makes use of the mbstring extension if 572 * available 573 * 574 * ```php 575 * echo Phalcon\Helper\Str::lower("HELLO"); // hello 576 * ``` 577 * 578 * @param string $text 579 * @param string $encoding 580 * 581 * @return string 582 */ 583 final public static function lower(string! text, string! encoding = "UTF-8") -> string 584 { 585 /** 586 * 'lower' checks for the mbstring extension to make a correct lowercase 587 * transformation 588 */ 589 if function_exists("mb_strtolower") { 590 return mb_strtolower(text, encoding); 591 } 592 593 return text->lower(); 594 } 595 596 /** 597 * Generates a random string based on the given type. Type is one of the 598 * RANDOM_* constants 599 * 600 * ```php 601 * use Phalcon\Helper\Str; 602 * 603 * echo Str::random(Str::RANDOM_ALNUM); // "aloiwkqz" 604 * ``` 605 * 606 * @param int $type 607 * @param int $length 608 * 609 * @return string 610 */ 611 final public static function random(int type = 0, long length = 8) -> string 612 { 613 var pool; 614 int end; 615 string text = ""; 616 617 switch type { 618 619 case Str::RANDOM_ALPHA: 620 let pool = array_merge(range("a", "z"), range("A", "Z")); 621 break; 622 623 case Str::RANDOM_HEXDEC: 624 let pool = array_merge(range(0, 9), range("a", "f")); 625 break; 626 627 case Str::RANDOM_NUMERIC: 628 let pool = range(0, 9); 629 break; 630 631 case Str::RANDOM_NOZERO: 632 let pool = range(1, 9); 633 break; 634 635 case Str::RANDOM_DISTINCT: 636 let pool = str_split("2345679ACDEFHJKLMNPRSTUVWXYZ"); 637 break; 638 639 default: 640 // RANDOM_ALNUM 641 let pool = array_merge( 642 range(0, 9), 643 range("a", "z"), 644 range("A", "Z") 645 ); 646 647 break; 648 } 649 650 let end = count(pool) - 1; 651 652 while strlen(text) < length { 653 let text .= pool[mt_rand(0, end)]; 654 } 655 656 return text; 657 } 658 659 /** 660 * Reduces multiple slashes in a string to single slashes 661 * 662 * ```php 663 * // foo/bar/baz 664 * echo Phalcon\Helper\Str::reduceSlashes("foo//bar/baz"); 665 * 666 * // http://foo.bar/baz/buz 667 * echo Phalcon\Helper\Str::reduceSlashes("http://foo.bar///baz/buz"); 668 * ``` 669 * 670 * @param string $text 671 * 672 * @return string 673 */ 674 final public static function reduceSlashes(string! text) -> string 675 { 676 return preg_replace("#(?<!:)//+#", "/", text); 677 } 678 679 /** 680 * Check if a string starts with a given string 681 * 682 * ```php 683 * use Phalcon\Helper\Str; 684 * 685 * echo Str::startsWith("Hello", "He"); // true 686 * echo Str::startsWith("Hello", "he", false); // false 687 * echo Str::startsWith("Hello", "he"); // true 688 * ``` 689 * 690 * @param string $text 691 * @param string $start 692 * @param bool $ignoreCase 693 * 694 * @return bool 695 */ 696 final public static function startsWith(string! text, string! start, bool ignoreCase = true) -> bool 697 { 698 return starts_with(text, start, ignoreCase); 699 } 700 701 /** 702 * Uncamelize strings which are camelized 703 * 704 * ```php 705 * use Phalcon\Helper\Str; 706 * 707 * echo Str::uncamelize("CocoBongo"); // coco_bongo 708 * echo Str::uncamelize("CocoBongo", "-"); // coco-bongo 709 * ``` 710 * 711 * @param string $text 712 * @param mixed $delimiter 713 * 714 * @return string 715 */ 716 final public static function uncamelize(string! text, var delimiter = null) -> string 717 { 718 return text->uncamelize(delimiter); 719 } 720 721 /** 722 * Makes a phrase underscored instead of spaced 723 * 724 * ```php 725 * use Phalcon\Helper\Str; 726 * 727 * echo Str::underscore("look behind"); // "look_behind" 728 * echo Str::underscore("Awesome Phalcon"); // "Awesome_Phalcon" 729 * ``` 730 * 731 * @param string $text 732 * 733 * @return string 734 */ 735 final public static function underscore(string! text) -> string 736 { 737 return preg_replace("#\s+#", "_", trim(text)); 738 } 739 740 /** 741 * Uppercases a string, this function makes use of the mbstring extension if 742 * available 743 * 744 * ```php 745 * echo Phalcon\Helper\Str::upper("hello"); // HELLO 746 * ``` 747 * 748 * @param string $text 749 * @param string $encoding 750 * 751 * @return string 752 */ 753 final public static function upper(string! text, string! encoding = "UTF-8") -> string 754 { 755 /** 756 * 'upper' checks for the mbstring extension to make a correct lowercase 757 * transformation 758 */ 759 if function_exists("mb_strtoupper") { 760 return mb_strtoupper(text, encoding); 761 } 762 763 return text->upper(); 764 } 765} 766