From caddf96a82c05d1fae95219c541430817dc156b2 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 6 Apr 2019 19:10:32 -0400 Subject: [PATCH] [#13954] - Added more helper methods for Str and Arr --- phalcon/Helper/Arr.zep | 137 +++++++++++ phalcon/Helper/Str.zep | 517 +++++++++++++++++++++++++++++++++++++++++ phalcon/Text.zep | 166 +++---------- 3 files changed, 685 insertions(+), 135 deletions(-) create mode 100644 phalcon/Helper/Str.zep diff --git a/phalcon/Helper/Arr.zep b/phalcon/Helper/Arr.zep index c43ca4a541a..a5a6d765dfc 100644 --- a/phalcon/Helper/Arr.zep +++ b/phalcon/Helper/Arr.zep @@ -19,6 +19,54 @@ use Phalcon\Helper\Exception; */ class Arr { + /** + * Chunks an array into smaller arrays of a specified size. + * + * @param array $collection + * @param int $size + * @param bool $preserveKeys + */ + final public static function chunk(array! collection, int size, bool preserveKeys = false) -> array + { + return array_chunk(collection, size, preserveKeys); + } + + /** + * Returns the head of a list. + * + * @param array $items + */ + final public static function first(array! collection) + { + return reset(collection); + } + + /** + * Flattens an array up to the one level depth, unless `$deep` is set to `true` + * + * @param array $collection + * @param bool $deep + */ + final public static function flatten(array! collection, bool deep = false) -> array + { + var data, item; + + let data = []; + for item in collection { + if typeof item !== "array" { + let data[] = item; + } else { + if deep { + let data = array_merge(data, self::flatten(item, true)); + } else { + let data = array_merge(data, array_values(item)); + } + } + } + + return data; + } + /** * Helper method to get an array element or a default */ @@ -41,6 +89,63 @@ class Arr return isset collection[index]; } + /** + * Checks a flat list for duplicate values. Returns true if duplicate + * values exist and false if values are all unique. + * + * @param array $items + * + * @return bool + */ + final public static function isUnique(array! collection) -> bool + { + return count(collection) === count(array_unique(collection)); + } + + /** + * Returns the last element in an array. + * + * @param array $items + */ + final public static function last(array! collection) + { + return end(collection); + } + + /** + * Sorts a collection of arrays or objects by key + * + * @param array $items + * @param [type] $attr + * @param [type] $order + * + * @return array + */ + final public static function order(array! collection, var attribute, string order = "asc") -> array + { + var item, key; + array sorted; + + let sorted = []; + for item in collection { + if typeof item === "object" { + let key = item->{attribute}; + } else { + let key = item[attribute]; + } + + let sorted[key] = item; + } + + if order === "asc" { + ksort(sorted); + } else { + krsort(sorted); + } + + return array_values(sorted); + } + /** * Helper method to set an array element */ @@ -54,4 +159,36 @@ class Arr return collection; } + + /** + * Returns a new array with n elements removed from the right. + * + * @param array $collection + * @param int $elements + */ + final public static function sliceLeft(array! collection, int elements = 1) -> array + { + return array_slice(collection, 0, elements); + } + + /** + * Returns a new array with the X elements from the right + * + * @param array $collection + * @param int $elements + */ + final public static function sliceRight(array! collection, int elements = 1) -> array + { + return array_slice(collection, elements); + } + + /** + * Returns all elements in an array except for the first one. + * + * @param array $collection + */ + final public static function tail(array! collection) -> array + { + return count(collection) > 1 ? array_slice(collection, 1) : collection; + } } diff --git a/phalcon/Helper/Str.zep b/phalcon/Helper/Str.zep new file mode 100644 index 00000000000..d4298d17d95 --- /dev/null +++ b/phalcon/Helper/Str.zep @@ -0,0 +1,517 @@ + +/** + * This file is part of the Phalcon. + * + * (c) Phalcon Team + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Phalcon\Helper; + +use Phalcon\Helper\Arr; +use Phalcon\Helper\Exception; + +/** + * Phalcon\Helper\Str + * + * This class offers quick string functions throught the framework + */ +class Str +{ + const RANDOM_ALNUM = 0; + const RANDOM_ALPHA = 1; + const RANDOM_DISTINCT = 5; + const RANDOM_HEXDEC = 2; + const RANDOM_NOZERO = 4; + const RANDOM_NUMERIC = 3; + + /** + * Converts strings to camelize style + * + * + * echo Phalcon\Text::camelize("coco_bongo"); // CocoBongo + * echo Phalcon\Text::camelize("co_co-bon_go", "-"); // Co_coBon_go + * echo Phalcon\Text::camelize("co_co-bon_go", "_-"); // CoCoBonGo + * + */ + final public static function camelize(string! text, var delimiter = null) -> string + { + return text->camelize(delimiter); + } + + /** + * Concatenates strings using the separator only once without duplication in + * places concatenation + * + * + * $str = Phalcon\Text::concat( + * "/", + * "/tmp/", + * "/folder_1/", + * "/folder_2", + * "folder_3/" + * ); + * + * // /tmp/folder_1/folder_2/folder_3/ + * echo $str; + * + * + * @param string separator + * @param string a + * @param string b + * @param string ...N + */ + //final public static function concat(string! separator, string! a, string! b) -> string + final public static function concat() -> string + { + var argument, arguments, data, first, last, prefix, separator, suffix; + + let arguments = func_get_args(); + + if count(arguments) < 3 { + throw new Exception("concat needs at least three parameters"); + } + + let separator = Arr::first(arguments), + arguments = Arr::tail(arguments), + first = Arr::first(arguments), + last = Arr::last(arguments), + prefix = "", + suffix = "", + data = []; + + if self::startsWith(first, separator) { + let prefix = separator; + } + + if self::endsWith(last, separator) { + let suffix = separator; + } + + + for argument in arguments { + let data[] = rtrim(ltrim(argument, separator), separator); + } + + return prefix . implode(separator, data) . suffix; + } + + /** + * Retuns number of vowels in provided string. Uses a regular expression + * to count the number of vowels (A, E, I, O, U) in a string. + * + * @param string $string + * + * @return int + */ + final public static function countVowels(string! text) -> int + { + var matches; + + preg_match_all("/[aeiou]/i", text, matches); + + return count(matches[0]); + } + + /** + * Decapitalizes the first letter of the sring and then adds it with rest + * of the string. Omit the upperRest parameter to keep the rest of the + * string intact, or set it to true to convert to uppercase. + * + * @param string $string + * @param bool $upperRest + * + * @return string + */ + final public static function decapitalize(string! text, bool upperRest = false, string! encoding = "UTF-8") -> string + { + var substr, suffix; + + if function_exists("mb_substr") { + let substr = mb_substr(text, 1); + } else { + let substr = substr(text, 1); + } + + if upperRest { + if function_exists("mb_strtoupper") { + let suffix = mb_strtoupper(substr, encoding); + } else { + let suffix = substr->upper(); + } + } else { + let suffix = substr; + } + + if function_exists("mb_strtolower") { + return mb_strtolower(mb_substr(text, 0, 1), encoding) . suffix; + } else { + return strtolower(substr(text, 0, 1)) . suffix; + } + } + + /** + * Generates random text in accordance with the template + * + * + * // Hi my name is a Bob + * echo Phalcon\Text::dynamic("{Hi|Hello}, my name is a {Bob|Mark|Jon}!"); + * + * // Hi my name is a Jon + * echo Phalcon\Text::dynamic("{Hi|Hello}, my name is a {Bob|Mark|Jon}!"); + * + * // Hello my name is a Bob + * echo Phalcon\Text::dynamic("{Hi|Hello}, my name is a {Bob|Mark|Jon}!"); + * + * // Hello my name is a Zyxep + * echo Phalcon\Text::dynamic( + * "[Hi/Hello], my name is a [Zyxep/Mark]!", + * "[", "]", + * "/" + * ); + * + */ + final public static function dynamic( + string! text, + string! leftDelimiter = "{", + string! rightDelimiter = "}", + string! separator = "|" + ) -> string + { + var ldS, rdS, pattern, matches, match, words, word, sub; + + if substr_count(text, leftDelimiter) !== substr_count(text, rightDelimiter) { + throw new \RuntimeException( + "Syntax error in string \"" . text . "\"" + ); + } + + let ldS = preg_quote(leftDelimiter), + rdS = preg_quote(rightDelimiter), + pattern = "/" . ldS . "([^" . ldS . rdS . "]+)" . rdS . "/", + matches = []; + + if !preg_match_all(pattern, text, matches, 2) { + return text; + } + + if typeof matches == "array" { + for match in matches { + if !isset match[0] || !isset match[1] { + continue; + } + + let words = explode(separator, match[1]), + word = words[array_rand(words)], + sub = preg_quote(match[0], separator), + text = preg_replace("/" . sub . "/", word, text, 1); + } + } + + return text; + } + + /** + * Check if a string ends with a given string + * + * + * echo Phalcon\Text::endsWith("Hello", "llo"); // true + * echo Phalcon\Text::endsWith("Hello", "LLO", false); // false + * echo Phalcon\Text::endsWith("Hello", "LLO"); // true + * + */ + final public static function endsWith(string str, string end, bool ignoreCase = true) -> bool + { + return ends_with(str, end, ignoreCase); + } + + /** + * Returns the first string there is between the strings from the + * parameter start and end. + * + * @param string $haystack + * @param string $start + * @param string $end + * + * @return string + */ + final public static function firstStringBetween( + string! haystack, + string! start, + string! end + ) -> string + { + if function_exists("mb_strstr") { + return trim(mb_strstr(mb_strstr(haystack, start), end, true), start . end); + } else { + return trim(strstr(strstr(haystack, start), end, true), start . end); + } + } + + /** + * Makes an underscored or dashed phrase human-readable + * + * + * echo Phalcon\Text::humanize("start-a-horse"); // "start a horse" + * echo Phalcon\Text::humanize("five_cats"); // "five cats" + * + */ + final public static function humanize(string! text) -> string + { + return preg_replace("#[_-]+#", " ", trim(text)); + } + + /** + * Lets you determine whether or not a string includes another string. + * + * @param string $needle + * @param string $haystack + * + * @return bool + */ + final public static function includes(string! needle, string! haystack) -> bool + { + if function_exists("mb_strpos") { + return (bool) mb_strpos(haystack, needle); + } else { + return (bool) strpos(haystack, needle); + } + } + + /** + * Adds a number to a string or increment that number if it already is + * defined + * + * + * echo Phalcon\Text::increment("a"); // "a_1" + * echo Phalcon\Text::increment("a_1"); // "a_2" + * + */ + final public static function increment(string str, string separator = "_") -> string + { + var parts, number; + + let parts = explode(separator, str); + + if fetch number, parts[1] { + let number++; + } else { + let number = 1; + } + + return parts[0] . separator. number; + } + + /** + * Compare two strings and returns true if both strings are anagram, + * false otherwise. + * + * @param string $first + * @param string $second + * + * @return bool + */ + final public static function isAnagram(string! first, string! second) -> bool + { + return count_chars(first, 1) === count_chars(second, 1); + } + + /** + * Returns true if the given string is lower case, false otherwise. + * + * @param string text + * + * @return bool + */ + final public static function isLower(string! text, string! encoding = "UTF-8") -> bool + { + if function_exists("mb_strtolower") { + return text === mb_strtolower(text, encoding); + } else { + return text === text->lower(); + } + } + + /** + * Returns true if the given string is a palindrome, false otherwise. + * + * @param string $text + * + * @return bool + */ + final public static function isPalindrome(string! text) -> bool + { + return strrev(text) === text; + } + + /** + * Returns true if the given string is upper case, false otherwise. + * + * @param string text + * + * @return bool + */ + final public static function isUpper(string! text, string! encoding = "UTF-8") -> bool + { + if function_exists("mb_strtoupper") { + return text === mb_strtoupper(text, encoding); + } else { + return text === text->upper(); + } + } + + /** + * Lowercases a string, this function makes use of the mbstring extension if + * available + * + * + * echo Phalcon\Text::lower("HELLO"); // hello + * + */ + final public static function lower(string! str, string! encoding = "UTF-8") -> string + { + /** + * 'lower' checks for the mbstring extension to make a correct lowercase + * transformation + */ + if function_exists("mb_strtolower") { + return mb_strtolower(str, encoding); + } + + return str->lower(); + } + + /** + * Generates a random string based on the given type. Type is one of the + * RANDOM_* constants + * + * + * use Phalcon\Text; + * + * // "aloiwkqz" + * echo Text::random(Text::RANDOM_ALNUM); + * + */ + final public static function random(int type = 0, long length = 8) -> string + { + var pool, str = ""; + int end; + + switch type { + + case Str::RANDOM_ALPHA: + let pool = array_merge(range("a", "z"), range("A", "Z")); + break; + + case Str::RANDOM_HEXDEC: + let pool = array_merge(range(0, 9), range("a", "f")); + break; + + case Str::RANDOM_NUMERIC: + let pool = range(0, 9); + break; + + case Str::RANDOM_NOZERO: + let pool = range(1, 9); + break; + + case Str::RANDOM_DISTINCT: + let pool = str_split("2345679ACDEFHJKLMNPRSTUVWXYZ"); + break; + + default: + // Default type \Phalcon\Text::RANDOM_ALNUM + let pool = array_merge( + range(0, 9), + range("a", "z"), + range("A", "Z") + ); + + break; + } + + let end = count(pool) - 1; + + while strlen(str) < length { + let str .= pool[mt_rand(0, end)]; + } + + return str; + } + + /** + * Reduces multiple slashes in a string to single slashes + * + * + * echo Phalcon\Text::reduceSlashes("foo//bar/baz"); // foo/bar/baz + * echo Phalcon\Text::reduceSlashes("http://foo.bar///baz/buz"); // http://foo.bar/baz/buz + * + */ + final public static function reduceSlashes(string! text) -> string + { + return preg_replace("#(? + * echo Phalcon\Text::startsWith("Hello", "He"); // true + * echo Phalcon\Text::startsWith("Hello", "he", false); // false + * echo Phalcon\Text::startsWith("Hello", "he"); // true + * + */ + final public static function startsWith(string! text, string! start, bool ignoreCase = true) -> bool + { + return starts_with(text, start, ignoreCase); + } + + /** + * Uncamelize strings which are camelized + * + * + * echo Phalcon\Text::uncamelize("CocoBongo"); // coco_bongo + * echo Phalcon\Text::uncamelize("CocoBongo", "-"); // coco-bongo + * + */ + final public static function uncamelize(string! text, var delimiter = null) -> string + { + return text->uncamelize(delimiter); + } + + /** + * Makes a phrase underscored instead of spaced + * + * + * echo Phalcon\Text::underscore("look behind"); // "look_behind" + * echo Phalcon\Text::underscore("Awesome Phalcon"); // "Awesome_Phalcon" + * + */ + final public static function underscore(string! text) -> string + { + return preg_replace("#\s+#", "_", trim(text)); + } + + /** + * Uppercases a string, this function makes use of the mbstring extension if + * available + * + * + * echo Phalcon\Text::upper("hello"); // HELLO + * + */ + final public static function upper(string! text, string! encoding = "UTF-8") -> string + { + /** + * 'upper' checks for the mbstring extension to make a correct lowercase + * transformation + */ + if function_exists("mb_strtoupper") { + return mb_strtoupper(text, encoding); + } + + return text->upper(); + } +} diff --git a/phalcon/Text.zep b/phalcon/Text.zep index 5df14d22a96..4c7ebf7b87b 100644 --- a/phalcon/Text.zep +++ b/phalcon/Text.zep @@ -10,6 +10,8 @@ namespace Phalcon; +use Phalcon\Helper\Str; + /** * Phalcon\Text * @@ -33,9 +35,9 @@ abstract class Text * echo Phalcon\Text::camelize("co_co-bon_go", "_-"); // CoCoBonGo * */ - public static function camelize(string! str, var delimiter = null) -> string + public static function camelize(string! text, var delimiter = null) -> string { - return str->camelize(delimiter); + return Str::camelize(text, delimiter); } /** @@ -63,26 +65,11 @@ abstract class Text //public static function concat(string! separator, string! a, string! b) -> string public static function concat() -> string { - /** - * TODO: - * Remove after solve https://github.com/phalcon/zephir/issues/938, - * and also replace line 214 to 213 - */ - var separator, a, b; - let separator = func_get_arg(0), - a = func_get_arg(1), - b = func_get_arg(2); - //END - - var c; + var args; - if func_num_args() > 3 { - for c in array_slice(func_get_args(), 3) { - let b = rtrim(b, separator) . separator . ltrim(c, separator); - } - } + let args = func_get_args(); - return rtrim(a, separator) . separator . ltrim(b, separator); + return call_user_func_array("\\Phalcon\\Helper\\Str::concat", args); } /** @@ -106,39 +93,14 @@ abstract class Text * ); * */ - public static function dynamic(string! text, string! leftDelimiter = "{", string! rightDelimiter = "}", string! separator = "|") -> string + public static function dynamic( + string! text, + string! leftDelimiter = "{", + string! rightDelimiter = "}", + string! separator = "|" + ) -> string { - var ldS, rdS, pattern, matches, match, words, word, sub; - - if substr_count(text, leftDelimiter) !== substr_count(text, rightDelimiter) { - throw new \RuntimeException( - "Syntax error in string \"" . text . "\"" - ); - } - - let ldS = preg_quote(leftDelimiter), - rdS = preg_quote(rightDelimiter), - pattern = "/" . ldS . "([^" . ldS . rdS . "]+)" . rdS . "/", - matches = []; - - if !preg_match_all(pattern, text, matches, 2) { - return text; - } - - if typeof matches == "array" { - for match in matches { - if !isset match[0] || !isset match[1] { - continue; - } - - let words = explode(separator, match[1]), - word = words[array_rand(words)], - sub = preg_quote(match[0], separator), - text = preg_replace("/" . sub . "/", word, text, 1); - } - } - - return text; + return Str::dynamic(text, leftDelimiter, rightDelimiter, separator); } /** @@ -150,9 +112,9 @@ abstract class Text * echo Phalcon\Text::endsWith("Hello", "LLO"); // true * */ - public static function endsWith(string str, string end, bool ignoreCase = true) -> bool + public static function endsWith(string text, string end, bool ignoreCase = true) -> bool { - return ends_with(str, end, ignoreCase); + return Str::endsWith(text, end, ignoreCase); } /** @@ -165,7 +127,7 @@ abstract class Text */ public static function humanize(string! text) -> string { - return preg_replace("#[_-]+#", " ", trim(text)); + return Str::humanize(text); } /** @@ -177,19 +139,9 @@ abstract class Text * echo Phalcon\Text::increment("a_1"); // "a_2" * */ - public static function increment(string str, string separator = "_") -> string + public static function increment(string text, string separator = "_") -> string { - var parts, number; - - let parts = explode(separator, str); - - if fetch number, parts[1] { - let number++; - } else { - let number = 1; - } - - return parts[0] . separator. number; + return Str::increment(text, separator); } /** @@ -200,16 +152,9 @@ abstract class Text * echo Phalcon\Text::lower("HELLO"); // hello * */ - public static function lower(string! str, string! encoding = "UTF-8") -> string + public static function lower(string! text, string! encoding = "UTF-8") -> string { - /** - * 'lower' checks for the mbstring extension to make a correct lowercase - * transformation - */ - if function_exists("mb_strtolower") { - return mb_strtolower(str, encoding); - } - return strtolower(str); + return Str::lower(text, encoding); } /** @@ -221,9 +166,9 @@ abstract class Text * echo Phalcon\Text::startsWith("Hello", "he"); // true * */ - public static function startsWith(string str, string start, bool ignoreCase = true) -> bool + public static function startsWith(string text, string start, bool ignoreCase = true) -> bool { - return starts_with(str, start, ignoreCase); + return Str::startsWith(text, start, ignoreCase); } /** @@ -239,49 +184,7 @@ abstract class Text */ public static function random(int type = 0, long length = 8) -> string { - var pool, str = ""; - int end; - - switch type { - - case Text::RANDOM_ALPHA: - let pool = array_merge(range("a", "z"), range("A", "Z")); - break; - - case Text::RANDOM_HEXDEC: - let pool = array_merge(range(0, 9), range("a", "f")); - break; - - case Text::RANDOM_NUMERIC: - let pool = range(0, 9); - break; - - case Text::RANDOM_NOZERO: - let pool = range(1, 9); - break; - - case Text::RANDOM_DISTINCT: - let pool = str_split("2345679ACDEFHJKLMNPRSTUVWXYZ"); - break; - - default: - // Default type \Phalcon\Text::RANDOM_ALNUM - let pool = array_merge( - range(0, 9), - range("a", "z"), - range("A", "Z") - ); - - break; - } - - let end = count(pool) - 1; - - while strlen(str) < length { - let str .= pool[mt_rand(0, end)]; - } - - return str; + return Str::random(type, length); } /** @@ -292,9 +195,9 @@ abstract class Text * echo Phalcon\Text::reduceSlashes("http://foo.bar///baz/buz"); // http://foo.bar/baz/buz * */ - public static function reduceSlashes(string str) -> string + public static function reduceSlashes(string! text) -> string { - return preg_replace("#(? */ - public static function uncamelize(string! str, var delimiter = null) -> string + public static function uncamelize(string! text, var delimiter = null) -> string { - return str->uncamelize(delimiter); + return Str::uncamelize(text, delimiter); } /** @@ -320,7 +223,7 @@ abstract class Text */ public static function underscore(string! text) -> string { - return preg_replace("#\s+#", "_", trim(text)); + return Str::underscore(text); } /** @@ -331,15 +234,8 @@ abstract class Text * echo Phalcon\Text::upper("hello"); // HELLO * */ - public static function upper(string! str, string! encoding = "UTF-8") -> string + public static function upper(string! text, string! encoding = "UTF-8") -> string { - /** - * 'upper' checks for the mbstring extension to make a correct lowercase - * transformation - */ - if function_exists("mb_strtoupper") { - return mb_strtoupper(str, encoding); - } - return strtoupper(str); + return Str::upper(text, encoding); } }