From dbbe6c263bd9d19ad1e8658a2e049052f8c74797 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Jun 2025 18:13:59 +0900 Subject: [PATCH] RandomKey class update, add methods to strings, json, array class, fix phpstan errors in other classes, add tests --- src/Basic.php | 7 +- src/Check/Encoding.php | 12 +- src/Combined/ArrayHandler.php | 249 +++- src/Convert/Encoding.php | 4 +- src/Convert/Json.php | 17 + src/Convert/Strings.php | 216 +++- src/Create/Email.php | 13 + src/Create/RandomKey.php | 141 ++- src/Logging/Logging.php | 135 ++- src/UrlRequests/Curl.php | 2 +- ...edArrayHandlerFindArraysMissingKeyTest.php | 404 +++++++ ...LibsCombinedArrayHandlerKsortArrayTest.php | 333 ++++++ ...dArrayHandlerSelectArrayFromOptionTest.php | 383 +++++++ ...eLibsCombinedArrayHandlerSortArrayTest.php | 328 ++++++ .../CoreLibsCombinedArrayHandlerTest.php | 1002 ++++++++++++++++- ...oreLibsConvertStringsRegexValidateTest.php | 283 +++++ .../Convert/CoreLibsConvertStringsTest.php | 400 ++++++- .../Create/CoreLibsCreateRandomKeyTest.php | 204 ++-- .../Logging/CoreLibsLoggingLoggingTest.php | 67 +- 19 files changed, 3914 insertions(+), 286 deletions(-) create mode 100644 test/phpunit/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php create mode 100644 test/phpunit/Combined/CoreLibsCombinedArrayHandlerKsortArrayTest.php create mode 100644 test/phpunit/Combined/CoreLibsCombinedArrayHandlerSelectArrayFromOptionTest.php create mode 100644 test/phpunit/Combined/CoreLibsCombinedArrayHandlerSortArrayTest.php create mode 100644 test/phpunit/Convert/CoreLibsConvertStringsRegexValidateTest.php diff --git a/src/Basic.php b/src/Basic.php index 1eb3410..ef93875 100644 --- a/src/Basic.php +++ b/src/Basic.php @@ -383,7 +383,8 @@ class Basic public function initRandomKeyLength(int $key_length): bool { trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\RandomKey::setRandomKeyLength()', E_USER_DEPRECATED); - return \CoreLibs\Create\RandomKey::setRandomKeyLength($key_length); + // no op, we do no longer pre set the random key length + return true; } /** @@ -988,10 +989,10 @@ class Basic * @param bool $auto_check default true, if source encoding is set * check that the source is actually matching * to what we sav the source is - * @return string encoding converted string + * @return string|false encoding converted string * @deprecated use \CoreLibs\Convert\Encoding::convertEncoding() instead */ - public static function convertEncoding(string $string, string $to_encoding, string $source_encoding = '', bool $auto_check = true): string + public static function convertEncoding(string $string, string $to_encoding, string $source_encoding = '', bool $auto_check = true): string|false { trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Encoding::convertEncoding()', E_USER_DEPRECATED); return \CoreLibs\Convert\Encoding::convertEncoding($string, $to_encoding, $source_encoding, $auto_check); diff --git a/src/Check/Encoding.php b/src/Check/Encoding.php index 577cf9b..08cb9df 100644 --- a/src/Check/Encoding.php +++ b/src/Check/Encoding.php @@ -56,7 +56,11 @@ class Encoding { // return mb_substitute_character(); if ($return_substitute_func === true) { - return mb_substitute_character(); + // if false abort with error + if (($return = mb_substitute_character()) === false) { + return self::$mb_error_char; + } + return $return; } else { return self::$mb_error_char; } @@ -88,7 +92,13 @@ class Encoding ): array|false { // convert to target encoding and convert back $temp = mb_convert_encoding($string, $to_encoding, $from_encoding); + if ($temp === false) { + return false; + } $compare = mb_convert_encoding($temp, $from_encoding, $to_encoding); + if ($compare === false) { + return false; + } // if string does not match anymore we have a convert problem if ($string == $compare) { return false; diff --git a/src/Combined/ArrayHandler.php b/src/Combined/ArrayHandler.php index dae3e1e..4bf3b99 100644 --- a/src/Combined/ArrayHandler.php +++ b/src/Combined/ArrayHandler.php @@ -10,6 +10,8 @@ namespace CoreLibs\Combined; class ArrayHandler { + public const string DATA_SEPARATOR = ':'; + /** * searches key = value in an array / array * only returns the first one found @@ -148,18 +150,22 @@ class ArrayHandler * array search simple. looks for key, value combination, if found, returns true * on default does not strict check, so string '4' will match int 4 and vica versa * - * @param array $array search in as array - * @param string|int $key key (key to search in) - * @param string|int|bool $value value (what to find) - * @param bool $strict [false], if set to true, will strict check key/value - * @return bool true on found, false on not found + * @param array $array search in as array + * @param string|int $key key (key to search in) + * @param string|int|bool|array $value values list (what to find) + * @param bool $strict [false], if set to true, will strict check key/value + * @return bool true on found, false on not found */ public static function arraySearchSimple( array $array, string|int $key, - string|int|bool $value, + string|int|bool|array $value, bool $strict = false ): bool { + // convert to array + if (!is_array($value)) { + $value = [$value]; + } foreach ($array as $_key => $_value) { // if value is an array, we search if (is_array($_value)) { @@ -167,9 +173,9 @@ class ArrayHandler if (($result = self::arraySearchSimple($_value, $key, $value, $strict)) !== false) { return $result; } - } elseif ($strict === false && $_key == $key && $_value == $value) { + } elseif ($strict === false && $_key == $key && in_array($_value, $value)) { return true; - } elseif ($strict === true && $_key === $key && $_value === $value) { + } elseif ($strict === true && $_key === $key && in_array($_value, $value, true)) { return true; } } @@ -236,6 +242,160 @@ class ArrayHandler return $hit_list; } + /** + * Search in an array for value with or without key and + * check in the same array block for the required key + * If not found return an array with the array block there the required key is missing, + * the path as string with seperator block set and the missing key entry + * + * @param array $array + * @param string|int|float|bool $search_value + * @param string|array $required_key + * @param ?string $search_key [null] + * @param string $path_separator [DATA_SEPARATOR] + * @param string $current_path + * @return array,path?:string,missing_key?:array}> + */ + public static function findArraysMissingKey( + array $array, + string|int|float|bool $search_value, + string|array $required_key, + ?string $search_key = null, + string $path_separator = self::DATA_SEPARATOR, + string $current_path = '' + ): array { + $results = []; + foreach ($array as $key => $value) { + $path = $current_path ? $current_path . $path_separator . $key : $key; + + if (is_array($value)) { + // Check if this array contains the search value + // either any value match or with key + if ($search_key === null) { + $containsValue = in_array($search_value, $value, true); + } else { + $containsValue = array_key_exists($search_key, $value) && $value[$search_key] === $search_value; + } + + // If it contains the value but doesn't have the required key + if ( + $containsValue && + ( + ( + is_string($required_key) && + !array_key_exists($required_key, $value) + ) || ( + is_array($required_key) && + count(array_intersect($required_key, array_keys($value))) !== count($required_key) + ) + ) + ) { + $results[] = [ + 'content' => $value, + 'path' => $path, + 'missing_key' => is_array($required_key) ? + array_values(array_diff($required_key, array_keys($value))) : + [$required_key] + ]; + } + + // Recursively search nested arrays + $results = array_merge( + $results, + self::findArraysMissingKey( + $value, + $search_value, + $required_key, + $search_key, + $path_separator, + $path + ) + ); + } + } + + return $results; + } + + /** + * Find key => value entry and return set with key for all matching + * Can search recursively through nested arrays if recursive flag is set + * + * @param array $array + * @param string $lookup + * @param int|string|float|bool $search + * @param bool $strict [false] + * @param bool $case_insensitive [false] + * @param bool $recursive [false] + * @param bool $flat_result [true] If set to false and recursive is on the result is a nested array + * @param string $flat_separator [DATA_SEPARATOR] if flat result is true, can be any string + * @return array + */ + public static function selectArrayFromOption( + array $array, + string $lookup, + int|string|float|bool $search, + bool $strict = false, + bool $case_insensitive = false, + bool $recursive = false, + bool $flat_result = true, + string $flat_separator = self::DATA_SEPARATOR + ): array { + // skip on empty + if ($array == []) { + return []; + } + // init return result + $result = []; + // case sensitive convert if string + if ($case_insensitive && is_string($search)) { + $search = strtolower($search); + } + + foreach ($array as $key => $value) { + // Handle current level search + if (isset($value[$lookup])) { + $compareValue = $value[$lookup]; + + if ($case_insensitive && is_string($compareValue)) { + $compareValue = strtolower($compareValue); + } + + if ( + ($strict && $search === $compareValue) || + (!$strict && $search == $compareValue) + ) { + $result[$key] = $value; + } + } + // Handle recursive search if flag is set + if ($recursive && is_array($value)) { + $recursiveResults = self::selectArrayFromOption( + $value, + $lookup, + $search, + $strict, + $case_insensitive, + true, + $flat_result, + $flat_separator + ); + + // Merge recursive results with current results + // Preserve keys by using array_merge with string keys or + operator + foreach ($recursiveResults as $recKey => $recValue) { + if ($flat_result) { + $result[$key . $flat_separator . $recKey] = $recValue; + } else { + $result[$key][$recKey] = $recValue; + } + } + } + } + + return $result; + } + /** * main wrapper function for next/prev key * @@ -553,12 +713,13 @@ class ArrayHandler } /** - * Modifieds the key of an array with a prefix and/or suffix and returns it with the original value + * Modifieds the key of an array with a prefix and/or suffix and + * returns it with the original value * does not change order in array * * @param array $in_array - * @param string $key_mod_prefix [default=''] key prefix string - * @param string $key_mod_suffix [default=''] key suffix string + * @param string $key_mod_prefix [''] key prefix string + * @param string $key_mod_suffix [''] key suffix string * @return array */ public static function arrayModifyKey( @@ -581,6 +742,72 @@ class ArrayHandler array_values($in_array) ); } + + /** + * sort array and return in same call + * sort ascending or descending with or without lower case convert + * value only, will loose key connections unless preserve_keys is set to true + * + * @param array $array array to sort by values + * @param int $params sort flags + * @return array + */ + public static function sortArray( + array $array, + bool $case_insensitive = false, + bool $reverse = false, + bool $maintain_keys = false, + int $params = SORT_REGULAR + ): array { + $fk_sort_lower_case = function (string $a, string $b): int { + return strtolower($a) <=> strtolower($b); + }; + $fk_sort_lower_case_reverse = function (string $a, string $b): int { + return strtolower($b) <=> strtolower($a); + }; + $case_insensitive ? ( + $maintain_keys ? + (uasort($array, $reverse ? $fk_sort_lower_case_reverse : $fk_sort_lower_case)) : + (usort($array, $reverse ? $fk_sort_lower_case_reverse : $fk_sort_lower_case)) + ) : + ( + $maintain_keys ? + ($reverse ? arsort($array, $params) : asort($array, $params)) : + ($reverse ? rsort($array, $params) : sort($array, $params)) + ); + return $array; + } + + /** + * sort by key ascending or descending and return + * + * @param array $array + * @param bool $case_insensitive [false] + * @param bool $reverse [false] + * @return array + */ + public static function ksortArray(array $array, bool $case_insensitive = false, bool $reverse = false): array + { + $fk_sort_lower_case = function (string $a, string $b): int { + return strtolower($a) <=> strtolower($b); + }; + $fk_sort_lower_case_reverse = function (string $a, string $b): int { + return strtolower($b) <=> strtolower($a); + }; + $fk_sort = function (string $a, string $b): int { + return $a <=> $b; + }; + $fk_sort_reverse = function (string $a, string $b): int { + return $b <=> $a; + }; + uksort( + $array, + $case_insensitive ? + ($reverse ? $fk_sort_lower_case_reverse : $fk_sort_lower_case) : + ($reverse ? $fk_sort_reverse : $fk_sort) + ); + return $array; + } } // __END__ diff --git a/src/Convert/Encoding.php b/src/Convert/Encoding.php index 26e1a9d..a0a80bf 100644 --- a/src/Convert/Encoding.php +++ b/src/Convert/Encoding.php @@ -23,14 +23,14 @@ class Encoding * @param bool $auto_check default true, if source encoding is set * check that the source is actually matching * to what we sav the source is - * @return string encoding converted string + * @return string|false encoding converted string or false on error */ public static function convertEncoding( string $string, string $to_encoding, string $source_encoding = '', bool $auto_check = true - ): string { + ): string|false { // set if not given if (!$source_encoding) { $source_encoding = mb_detect_encoding($string); diff --git a/src/Convert/Json.php b/src/Convert/Json.php index 7f885df..356e5db 100644 --- a/src/Convert/Json.php +++ b/src/Convert/Json.php @@ -119,6 +119,23 @@ class Json } return $return_string === true ? $json_error_string : self::$json_last_error; } + + /** + * wrapper to call convert array to json with pretty print + * + * @param array $data + * @return string + */ + public static function jsonPrettyPrint(array $data): string + { + return self::jsonConvertArrayTo( + $data, + JSON_PRETTY_PRINT | + JSON_UNESCAPED_LINE_TERMINATORS | + JSON_UNESCAPED_SLASHES | + JSON_UNESCAPED_UNICODE + ); + } } // __END__ diff --git a/src/Convert/Strings.php b/src/Convert/Strings.php index 8163239..30d4ad4 100644 --- a/src/Convert/Strings.php +++ b/src/Convert/Strings.php @@ -8,8 +8,20 @@ declare(strict_types=1); namespace CoreLibs\Convert; +use CoreLibs\Combined\ArrayHandler; + class Strings { + /** @var array all the preg error messages */ + public const array PREG_ERROR_MESSAGES = [ + PREG_NO_ERROR => 'No error', + PREG_INTERNAL_ERROR => 'Internal PCRE error', + PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit exhausted', + PREG_RECURSION_LIMIT_ERROR => 'Recursion limit exhausted', + PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data', + PREG_BAD_UTF8_OFFSET_ERROR => 'Bad UTF-8 offset', + PREG_JIT_STACKLIMIT_ERROR => 'JIT stack limit exhausted' + ]; /** * return the number of elements in the split list * 0 if nothing / invalid split @@ -52,29 +64,42 @@ class Strings * Note a string LONGER then the maxium will be attached with the LAST * split character. In above exmaple * ABCD1234EFGHTOOLONG will be ABCD-1234-EFGH-TOOLONG + * If the characters are NOT ASCII it will return the string as is * - * @param string $value string value to split + * @param string $string string value to split * @param string $split_format split format - * @param string $split_characters list of charcters with which we split - * if not set uses dash ('-') * @return string split formatted string or original value if not chnaged + * @throws \InvalidArgumentException for empty split format, invalid values, split characters or split format */ public static function splitFormatString( - string $value, + string $string, string $split_format, - string $split_characters = '-' ): string { - if ( - // abort if split format is empty - empty($split_format) || - // if not in the valid ASCII character range for any of the strings - preg_match('/[^\x20-\x7e]/', $value) || - // preg_match('/[^\x20-\x7e]/', $split_format) || - preg_match('/[^\x20-\x7e]/', $split_characters) || - // only numbers and split characters in split_format - !preg_match("/[0-9" . $split_characters . "]/", $split_format) - ) { - return $value; + // skip if string or split format is empty is empty + if (empty($string) || empty($split_format)) { + return $string; + } + if (preg_match('/[^\x20-\x7e]/', $string)) { + throw new \InvalidArgumentException( + "The string to split can only be ascii characters: " . $string + ); + } + // get the split characters that are not numerical and check they are ascii + $split_characters = self::removeDuplicates(preg_replace('/[0-9]/', '', $split_format) ?: ''); + if (empty($split_characters)) { + throw new \InvalidArgumentException( + "A split character must exist in the format string: " . $split_format + ); + } + if (preg_match('/[^\x20-\x7e]/', $split_characters)) { + throw new \InvalidArgumentException( + "The split character has to be a valid ascii character: " . $split_characters + ); + } + if (!preg_match("/^[0-9" . $split_characters . "]+$/", $split_format)) { + throw new \InvalidArgumentException( + "The split format can only be numbers and the split characters: " . $split_format + ); } // split format list $split_list = preg_split( @@ -86,14 +111,14 @@ class Strings ); // if this is false, or only one array, abort split if (!is_array($split_list) || count($split_list) == 1) { - return $value; + return $string; } $out = ''; $pos = 0; $last_split = ''; foreach ($split_list as $offset) { if (is_numeric($offset)) { - $_part = substr($value, $pos, (int)$offset); + $_part = substr($string, $pos, (int)$offset); if (empty($_part)) { break; } @@ -104,8 +129,8 @@ class Strings $last_split = $offset; } } - if (!empty($out) && $pos < strlen($value)) { - $out .= $last_split . substr($value, $pos); + if (!empty($out) && $pos < strlen($string)) { + $out .= $last_split . substr($string, $pos); } // if last is not alphanumeric remove, remove if (!strcspn(substr($out, -1, 1), $split_characters)) { @@ -115,10 +140,49 @@ class Strings if (!empty($out)) { return $out; } else { - return $value; + return $string; } } + /** + * Split a string into n-length blocks with a split character inbetween + * This is simplified version from splitFormatString that uses + * fixed split length with a characters, this evenly splits the string out into the + * given length + * This works with non ASCII characters too + * + * @param string $string string to split + * @param int $split_length split length, must be smaller than string and larger than 0 + * @param string $split_characters [default=-] the character to split, can be more than one + * @return string + * @throws \InvalidArgumentException Thrown if split length style is invalid + */ + public static function splitFormatStringFixed( + string $string, + int $split_length, + string $split_characters = '-' + ): string { + // if empty string or if split lenght is 0 or empty split characters + // then we skip any splitting + if (empty($string) || $split_length == 0 || empty($split_characters)) { + return $string; + } + $return_string = ''; + $string_length = mb_strlen($string); + // check that the length is not too short + if ($split_length < 1 || $split_length >= $string_length) { + throw new \InvalidArgumentException( + "The split length must be at least 1 character and less than the string length to split. " + . "Split length: " . $split_length . ", string length: " . $string_length + ); + } + for ($i = 0; $i < $string_length; $i += $split_length) { + $return_string .= mb_substr($string, $i, $split_length) . $split_characters; + } + // remove last trailing character which is always the split char length + return mb_substr($return_string, 0, -1 * mb_strlen($split_characters)); + } + /** * Strip any duplicated slahes from a path * eg: //foo///bar/foo.inc -> /foo/bar/foo.inc @@ -146,6 +210,116 @@ class Strings { return trim($text, pack('H*', 'EFBBBF')); } + + /** + * Make as string of characters unique + * + * @param string $string + * @return string + */ + public static function removeDuplicates(string $string): string + { + // combine again + $result = implode( + '', + // unique list + array_unique( + // split into array + mb_str_split($string) + ) + ); + + return $result; + } + + /** + * check if all characters are in set + * + * @param string $needle Needle to search + * @param string $haystack Haystack to search in + * @return bool True on found, False if not in haystack + */ + public static function allCharsInSet(string $needle, string $haystack): bool + { + $input_length = strlen($needle); + + for ($i = 0; $i < $input_length; $i++) { + if (strpos($haystack, $needle[$i]) === false) { + return false; + } + } + + return true; + } + + /** + * converts a list of arrays of strings into a string of unique entries + * input arrays can be nested, only values are used + * + * @param array ...$char_lists + * @return string + */ + public static function buildCharStringFromLists(array ...$char_lists): string + { + return implode('', array_unique( + ArrayHandler::flattenArray( + array_merge(...$char_lists) + ) + )); + } + + /** + * Check if a regex is valid. Does not return the detail regex parser error + * + * @param string $pattern Any regex string + * @return bool False on invalid regex + */ + public static function isValidRegex(string $pattern): bool + { + preg_last_error(); + try { + $var = ''; + @preg_match($pattern, $var); + return preg_last_error() === PREG_NO_ERROR; + } catch (\Error $e) { + return false; + } + } + + /** + * Returns the last preg error messages as string + * all messages are defined in PREG_ERROR_MESSAGES + * + * @return string + */ + public static function getLastRegexErrorString(): string + { + return self::PREG_ERROR_MESSAGES[preg_last_error()] ?? 'Unknown error'; + } + + /** + * check if a regex is invalid, returns array with flag and error string + * + * @param string $pattern + * @return array{valid:bool,preg_error:int,error:null|string,pcre_error:null|string} + */ + public static function validateRegex(string $pattern): array + { + // Clear any previous PCRE errors + preg_last_error(); + $var = ''; + if (@preg_match($pattern, $var) === false) { + $error = preg_last_error(); + return [ + 'valid' => false, + 'preg_error' => $error, + 'error' => self::PREG_ERROR_MESSAGES[$error] ?? 'Unknown error', + 'pcre_error' => preg_last_error_msg(), + ]; + } + + return ['valid' => true, 'preg_error' => PREG_NO_ERROR, 'error' => null, 'pcre_error' => null]; + } } // __END__ diff --git a/src/Create/Email.php b/src/Create/Email.php index 5e0c6f7..2f549bb 100644 --- a/src/Create/Email.php +++ b/src/Create/Email.php @@ -38,6 +38,7 @@ class Email * @param string $encoding Encoding, if not set UTF-8 * @param bool $kv_folding If set to true and a valid encoding, do KV folding * @return string Correctly encoded and build email string + * @throws \IntlException if email name cannot be converted to UTF-8 */ public static function encodeEmailName( string $email, @@ -52,6 +53,10 @@ class Email if ($encoding != 'UTF-8') { $email_name = mb_convert_encoding($email_name, $encoding, 'UTF-8'); } + // if we cannot transcode the name, return just the email + if ($email_name === false) { + throw new \IntlException('Cannot convert email_name to UTF-8'); + } $email_name = mb_encode_mimeheader( in_array($encoding, self::$encoding_kv_allowed) && $kv_folding ? @@ -77,6 +82,8 @@ class Email * @param bool $kv_folding If set to true and a valid encoding, * do KV folding * @return array Pos 0: Subject, Pos 1: Body + * @throws \IntlException if subject cannot be converted to UTF-8 + * @throws \IntlException if body cannot be converted to UTF-8 */ private static function replaceContent( string $subject, @@ -102,6 +109,12 @@ class Email $subject = mb_convert_encoding($subject, $encoding, 'UTF-8'); $body = mb_convert_encoding($body, $encoding, 'UTF-8'); } + if ($subject === false) { + throw new \IntlException('Cannot convert subject to UTF-8'); + } + if ($body === false) { + throw new \IntlException('Cannot convert body to UTF-8'); + } // we need to encodde the subject $subject = mb_encode_mimeheader( in_array($encoding, self::$encoding_kv_allowed) && $kv_folding ? diff --git a/src/Create/RandomKey.php b/src/Create/RandomKey.php index 6d57f75..31a5c5b 100644 --- a/src/Create/RandomKey.php +++ b/src/Create/RandomKey.php @@ -8,39 +8,97 @@ declare(strict_types=1); namespace CoreLibs\Create; +use CoreLibs\Convert\Strings; + class RandomKey { + /** @var int set the default key length it nothing else is set */ + public const int KEY_LENGTH_DEFAULT = 4; + /** @var int the maximum key length allowed */ + public const int KEY_LENGTH_MAX = 256; + /** @var string the default characters in the key range */ + public const string KEY_CHARACTER_RANGE_DEFAULT = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + . 'abcdefghijklmnopqrstuvwxyz' + . '0123456789'; // key generation - /** @var string */ - private static string $key_range = ''; - /** @var int */ - private static int $one_key_length; - /** @var int */ - private static int $key_length = 4; // default key length - /** @var int */ - private static int $max_key_length = 256; // max allowed length + /** @var string all the characters that are int he current radnom key range */ + private static string $key_character_range = ''; + /** @var int character count in they key character range */ + private static int $key_character_range_length = 0; + /** @var int default key lenghth */ + /** @deprecated Will be removed */ + private static int $key_length = 4; /** * if launched as class, init random key data first + * + * @param array ...$key_range */ - public function __construct() + public function __construct(array ...$key_range) { - $this->initRandomKeyData(); + $this->setRandomKeyData(...$key_range); } + + /** + * internal key range validation + * + * @param array ...$key_range + * @return string + */ + private static function validateRandomKeyData(array ...$key_range): string + { + $key_character_range = Strings::buildCharStringFromLists(...$key_range); + if (strlen(self::$key_character_range) <= 1) { + return ''; + } + return $key_character_range; + } + /** * sets the random key range with the default values * + * @param array $key_range a list of key ranges as array * @return void has no return + * @throws \LengthException If the string length is only 1 abort */ - private static function initRandomKeyData(): void + public static function setRandomKeyData(array ...$key_range): void { - // random key generation base string - self::$key_range = join('', array_merge( - range('A', 'Z'), - range('a', 'z'), - range('0', '9') - )); - self::$one_key_length = strlen(self::$key_range); + // if key range is not set + if (!count($key_range)) { + self::$key_character_range = self::KEY_CHARACTER_RANGE_DEFAULT; + } else { + self::$key_character_range = self::validateRandomKeyData(...$key_range); + // random key generation base string + } + self::$key_character_range_length = strlen(self::$key_character_range); + if (self::$key_character_range_length <= 1) { + throw new \LengthException( + "The given key character range '" . self::$key_character_range . "' " + . "is too small, must be at lest two characters: " + . self::$key_character_range_length + ); + } + } + + /** + * get the characters for the current key characters + * + * @return string + */ + public static function getRandomKeyData(): string + { + return self::$key_character_range; + } + + /** + * get the length of all random characters + * + * @return int + */ + public static function getRandomKeyDataLength(): int + { + return self::$key_character_range_length; } /** @@ -53,7 +111,7 @@ class RandomKey { if ( $key_length > 0 && - $key_length <= self::$max_key_length + $key_length <= self::KEY_LENGTH_MAX ) { return true; } else { @@ -67,6 +125,7 @@ class RandomKey * * @param int $key_length key length * @return bool true/false for set status + * @deprecated This function does no longer set the key length, the randomKeyGen parameter has to b used */ public static function setRandomKeyLength(int $key_length): bool { @@ -83,6 +142,7 @@ class RandomKey * get the current set random key length * * @return int Current set key length + * @deprecated Key length is set during randomKeyGen call, this nethid is deprecated */ public static function getRandomKeyLength(): int { @@ -94,28 +154,37 @@ class RandomKey * if override key length is set, it will check on valid key and use this * this will not set the class key length variable * - * @param int $key_length key length override, -1 for use default - * @return string random key + * @param int $key_length [default=-1] key length override, + * if not set use default [LEGACY] + * @param array $key_range a list of key ranges as array, + * if not set use previous set data + * @return string random key */ - public static function randomKeyGen(int $key_length = -1): string - { - // init random key strings if not set - if ( - !isset(self::$one_key_length) - ) { - self::initRandomKeyData(); - } - $use_key_length = 0; - // only if valid int key with valid length - if (self::validateRandomKeyLenght($key_length) === true) { - $use_key_length = $key_length; + public static function randomKeyGen( + int $key_length = self::KEY_LENGTH_DEFAULT, + array ...$key_range + ): string { + $key_character_range = ''; + if (count($key_range)) { + $key_character_range = self::validateRandomKeyData(...$key_range); + $key_character_range_length = strlen($key_character_range); } else { - $use_key_length = self::$key_length; + if (!self::$key_character_range_length) { + self::setRandomKeyData(); + } + $key_character_range = self::getRandomKeyData(); + $key_character_range_length = self::getRandomKeyDataLength(); + } + // if not valid key length, fallback to default + if (!self::validateRandomKeyLenght($key_length)) { + $key_length = self::KEY_LENGTH_DEFAULT; } // create random string $random_string = ''; - for ($i = 1; $i <= $use_key_length; $i++) { - $random_string .= self::$key_range[random_int(0, self::$one_key_length - 1)]; + for ($i = 1; $i <= $key_length; $i++) { + $random_string .= $key_character_range[ + random_int(0, $key_character_range_length - 1) + ]; } return $random_string; } diff --git a/src/Logging/Logging.php b/src/Logging/Logging.php index 7476ef5..0f9c498 100644 --- a/src/Logging/Logging.php +++ b/src/Logging/Logging.php @@ -35,6 +35,7 @@ class Logging /** @var string log file block separator, not changeable */ private const LOG_FILE_BLOCK_SEPARATOR = '.'; + // MARK: OPTION array // NOTE: the second party array{} hs some errors /** @var array>|array{string:array{type:string,type_info?:string,mandatory:true,alias?:string,default:string|bool|Level,deprecated:bool,use?:string}} */ private const OPTIONS = [ @@ -50,6 +51,7 @@ class Logging 'type' => 'string', 'mandatory' => false, 'default' => '', 'deprecated' => true, 'use' => 'log_file_id' ], + // log level 'log_level' => [ 'type' => 'instance', 'type_info' => '\CoreLibs\Logging\Logger\Level', @@ -57,6 +59,14 @@ class Logging 'default' => Level::Debug, 'deprecated' => false ], + // level to trigger write to error_log + 'error_log_write_level' => [ + 'type' => 'instance', + 'type_info' => '\CoreLibs\Logging\Logger\Level', + 'mandatory' => false, + 'default' => Level::Emergency, + 'deprecated' => false, + ], // options 'log_per_run' => [ 'type' => 'bool', 'mandatory' => false, @@ -92,8 +102,10 @@ class Logging /** @var array */ private array $options = []; - /** @var Level set level */ + /** @var Level set logging level */ private Level $log_level; + /** @var Level set level for writing to error_log, will not write if log level lower than error log write level */ + private Level $error_log_write_level; // page and host name /** @var string */ @@ -145,12 +157,13 @@ class Logging ]; /** - * Init logger + * MARK: Init logger * * options array layout * - log_folder: * - log_file_id / file_id (will be deprecated): * - log_level: + * - error_log_write_level: at what level we write to error_log * * - log_per_run: * - log_per_date: (was print_file_date) @@ -172,6 +185,8 @@ class Logging // set log level $this->initLogLevel(); + // set error log write level + $this->initErrorLogWriteLevel(); // set log folder from options $this->initLogFolder(); // set per run UID for logging @@ -190,8 +205,10 @@ class Logging // PRIVATE METHODS // ********************************************************************* + // MARK: options check + /** - * Undocumented function + * validate options * * @param array $options * @return bool @@ -263,6 +280,8 @@ class Logging return true; } + // MARK: init log elvels + /** * init log level, just a wrapper to auto set from options * @@ -280,6 +299,24 @@ class Logging $this->setLoggingLevel($this->options['log_level']); } + /** + * init error log write level + * + * @return void + */ + private function initErrorLogWriteLevel() + { + if ( + empty($this->options['error_log_write_level']) || + !$this->options['error_log_write_level'] instanceof Level + ) { + $this->options['error_log_write_level'] = Level::Emergency; + } + $this->setErrorLogWriteLevel($this->options['error_log_write_level']); + } + + // MARK: set log folder + /** * Set the log folder * If folder is not writeable the script will throw an E_USER_ERROR @@ -321,6 +358,8 @@ class Logging return $status; } + // MARK: set host name + /** * Set the hostname and port * If port is not defaul 80 it will be added to the host name @@ -337,6 +376,8 @@ class Logging } } + // MARK: set log file id (file) + /** * set log file prefix id * @@ -395,6 +436,8 @@ class Logging return $status; } + // MARK init log flags and levels + /** * set flags from options and option flags connection internal settings * @@ -423,6 +466,19 @@ class Logging return $this->log_level->includes($level); } + /** + * Checks that given level is matchins error_log write level + * + * @param Level $level + * @return bool + */ + private function checkErrorLogWriteLevel(Level $level): bool + { + return $this->error_log_write_level->includes($level); + } + + // MARK: build log ifle name + /** * Build the file name for writing * @@ -490,6 +546,8 @@ class Logging return $fn; } + // MARK: master write log to file + /** * writes error msg data to file for current level * @@ -507,6 +565,10 @@ class Logging if (!$this->checkLogLevel($level)) { return false; } + // if we match level then write to error_log + if ($this->checkErrorLogWriteLevel($level)) { + error_log((string)$message); + } // build logging file name // fn is log folder + file name @@ -531,6 +593,8 @@ class Logging return true; } + // MARK: master prepare log + /** * Prepare the log message with all needed info blocks: * [timestamp] [host name] [file path + file::row number] [running uid] {class::/->method} @@ -610,6 +674,7 @@ class Logging // PUBLIC STATIC METHJODS // ********************************************************************* + // MARK: set log level /** * set the log level * @@ -670,7 +735,7 @@ class Logging // **** GET/SETTER - // log level set and get + // MARK: log level /** * set new log level @@ -705,7 +770,30 @@ class Logging ); } - // log file id set (file name prefix) + // MARK: error log write level + + /** + * set the error_log write level + * + * @param string|int|Level $level + * @return void + */ + public function setErrorLogWriteLevel(string|int|Level $level): void + { + $this->error_log_write_level = $this->processLogLevel($level); + } + + /** + * get the current level for error_log write + * + * @return Level + */ + public function getErrorLogWriteLevel(): Level + { + return $this->error_log_write_level; + } + + // MARK: log file id set (file name prefix) /** * sets the internal log file prefix id @@ -733,7 +821,7 @@ class Logging return $this->log_file_id; } - // log unique id set (for per run) + // MARK: log unique id set (for per run) /** * Sets a unique id based on current date (y/m/d, h:i:s) and a unique id (8 chars) @@ -768,7 +856,7 @@ class Logging return $this->log_file_unique_id; } - // general log date + // MARK: general log date /** * set the log file date to Y-m-d @@ -791,7 +879,7 @@ class Logging return $this->log_file_date; } - // general flag set + // MARK: general flag set /** * set one of the basic flags @@ -846,7 +934,7 @@ class Logging return $this->log_flags; } - // log folder/file + // MARK: log folder/file /** * set new log folder, check that folder is writeable @@ -890,7 +978,7 @@ class Logging return $this->log_file_name; } - // max log file size + // MARK: max log file size /** * set mag log file size @@ -921,7 +1009,7 @@ class Logging } // ********************************************************************* - // OPTIONS CALLS + // MARK: OPTIONS CALLS // ********************************************************************* /** @@ -939,6 +1027,8 @@ class Logging // MAIN CALLS // ********************************************************************* + // MARK: main log call + /** * Commong log interface * @@ -976,7 +1066,7 @@ class Logging } /** - * DEBUG: 100 + * MARK: DEBUG: 100 * * write debug data to error_msg array * @@ -1008,7 +1098,7 @@ class Logging } /** - * INFO: 200 + * MARK: INFO: 200 * * @param string|Stringable $message * @param mixed[] $context @@ -1027,7 +1117,7 @@ class Logging } /** - * NOTICE: 250 + * MARK: NOTICE: 250 * * @param string|Stringable $message * @param mixed[] $context @@ -1046,7 +1136,7 @@ class Logging } /** - * WARNING: 300 + * MARK: WARNING: 300 * * @param string|Stringable $message * @param mixed[] $context @@ -1065,7 +1155,7 @@ class Logging } /** - * ERROR: 400 + * MARK: ERROR: 400 * * @param string|Stringable $message * @param mixed[] $context @@ -1084,7 +1174,7 @@ class Logging } /** - * CTRITICAL: 500 + * MARK: CTRITICAL: 500 * * @param string|Stringable $message * @param mixed[] $context @@ -1103,7 +1193,7 @@ class Logging } /** - * ALERT: 550 + * MARK: ALERT: 550 * * @param string|Stringable $message * @param mixed[] $context @@ -1122,7 +1212,7 @@ class Logging } /** - * EMERGENCY: 600 + * MARK: EMERGENCY: 600 * * @param string|Stringable $message * @param mixed[] $context @@ -1141,7 +1231,7 @@ class Logging } // ********************************************************************* - // DEPRECATED SUPPORT CALLS + // MARK: DEPRECATED SUPPORT CALLS // ********************************************************************* // legacy, but there are too many implemented @@ -1199,7 +1289,7 @@ class Logging } // ********************************************************************* - // DEPRECATED METHODS + // MARK: DEPRECATED METHODS // ********************************************************************* /** @@ -1365,7 +1455,7 @@ class Logging } // ********************************************************************* - // DEBUG METHODS + // MARK: DEBUG METHODS // ********************************************************************* /** @@ -1398,6 +1488,7 @@ class Logging } // back to options level $this->initLogLevel(); + $this->initErrorLogWriteLevel(); print "OPT set level: " . $this->getLoggingLevel()->getName() . "
"; } } diff --git a/src/UrlRequests/Curl.php b/src/UrlRequests/Curl.php index ced5129..40b306f 100644 --- a/src/UrlRequests/Curl.php +++ b/src/UrlRequests/Curl.php @@ -599,7 +599,7 @@ class Curl implements Interface\RequestsInterface // for post we set POST option if ($type == "post") { curl_setopt($handle, CURLOPT_POST, true); - } elseif (!empty($type) && in_array($type, self::CUSTOM_REQUESTS)) { + } elseif (in_array($type, self::CUSTOM_REQUESTS)) { curl_setopt($handle, CURLOPT_CUSTOMREQUEST, strtoupper($type)); } // set body data if not null, will send empty [] for empty data diff --git a/test/phpunit/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php b/test/phpunit/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php new file mode 100644 index 0000000..9553c0a --- /dev/null +++ b/test/phpunit/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php @@ -0,0 +1,404 @@ + [ + 'name' => 'John', + 'age' => 25 + // missing 'email' key + ], + 'item2' => [ + 'name' => 'Jane', + 'age' => 30, + 'email' => 'jane@example.com' + ], + 'item3' => [ + 'name' => 'John', // same value as item1 + 'age' => 35, + 'email' => 'john2@example.com' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'John', 'email'); + + $this->assertCount(1, $result); + $this->assertEquals($array['item1'], $result[0]['content']); + $this->assertEquals('item1', $result[0]['path']); + $this->assertEquals(['email'], $result[0]['missing_key']); + } + + /** + * Test finding missing single key when searching by specific key-value pair + */ + public function testFindMissingSingleKeyWithKeyValueSearch() + { + $array = [ + 'user1' => [ + 'id' => 1, + 'name' => 'Alice' + // missing 'status' key + ], + 'user2' => [ + 'id' => 2, + 'name' => 'Bob', + 'status' => 'active' + ], + 'user3' => [ + 'id' => 1, // same id as user1 + 'name' => 'Charlie', + 'status' => 'inactive' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 1, 'status', 'id'); + + $this->assertCount(1, $result); + $this->assertEquals($array['user1'], $result[0]['content']); + $this->assertEquals('user1', $result[0]['path']); + $this->assertEquals(['status'], $result[0]['missing_key']); + } + + /** + * Test finding missing multiple keys + */ + public function testFindMissingMultipleKeys() + { + $array = [ + 'record1' => [ + 'name' => 'Test', + 'value' => 100 + // missing both 'date' and 'status' keys + ], + 'record2' => [ + 'name' => 'Test', + 'value' => 200, + 'date' => '2023-01-01' + // missing 'status' key + ], + 'record3' => [ + 'name' => 'Test', + 'value' => 300, + 'date' => '2023-01-02', + 'status' => 'complete' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'Test', ['date', 'status']); + + $this->assertCount(2, $result); + + // First result should be record1 missing both keys + $this->assertEquals($array['record1'], $result[0]['content']); + $this->assertEquals('record1', $result[0]['path']); + $this->assertContains('date', $result[0]['missing_key']); + $this->assertContains('status', $result[0]['missing_key']); + $this->assertCount(2, $result[0]['missing_key']); + + // Second result should be record2 missing status key + $this->assertEquals($array['record2'], $result[1]['content']); + $this->assertEquals('record2', $result[1]['path']); + $this->assertEquals(['status'], $result[1]['missing_key']); + } + + /** + * Test with nested arrays + */ + public function testFindMissingKeyInNestedArrays() + { + $array = [ + 'section1' => [ + 'items' => [ + 'item1' => [ + 'name' => 'Product A', + 'price' => 99.99 + // missing 'category' key + ], + 'item2' => [ + 'name' => 'Product B', + 'price' => 149.99, + 'category' => 'electronics' + ] + ] + ], + 'section2' => [ + 'data' => [ + 'name' => 'Product A', // same name as nested item + 'category' => 'books' + ] + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'Product A', 'category'); + + $this->assertCount(1, $result); + $this->assertEquals($array['section1']['items']['item1'], $result[0]['content']); + $this->assertEquals('section1:items:item1', $result[0]['path']); + $this->assertEquals(['category'], $result[0]['missing_key']); + } + + /** + * Test when no arrays are missing the required key + */ + public function testNoMissingKeys() + { + $array = [ + 'item1' => [ + 'name' => 'John', + 'email' => 'john@example.com' + ], + 'item2' => [ + 'name' => 'Jane', + 'email' => 'jane@example.com' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'John', 'email'); + + $this->assertEmpty($result); + } + + /** + * Test when search value is not found in any array + */ + public function testSearchValueNotFound() + { + $array = [ + 'item1' => [ + 'name' => 'John', + 'age' => 25 + ], + 'item2' => [ + 'name' => 'Jane', + 'age' => 30 + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'Bob', 'email'); + + $this->assertEmpty($result); + } + + /** + * Test with different data types for search value + */ + public function testDifferentSearchValueTypes() + { + $array = [ + 'item1' => [ + 'active' => true, + 'count' => 5 + // missing 'label' key + ], + 'item2' => [ + 'active' => false, + 'count' => 10, + 'label' => 'test' + ], + 'item3' => [ + 'active' => true, // same boolean as item1 + 'count' => 15, + 'label' => 'another' + ] + ]; + + // Test with boolean + $result = ArrayHandler::findArraysMissingKey($array, true, 'label', 'active'); + $this->assertCount(1, $result); + $this->assertEquals('item1', $result[0]['path']); + + // Test with integer + $result = ArrayHandler::findArraysMissingKey($array, 5, 'label', 'count'); + $this->assertCount(1, $result); + $this->assertEquals('item1', $result[0]['path']); + } + + /** + * Test with empty array + */ + public function testEmptyArray() + { + $array = []; + + $result = ArrayHandler::findArraysMissingKey($array, 'test', 'key'); + + $this->assertEmpty($result); + } + + /** + * Test with array containing non-array values + */ + public function testMixedArrayTypes() + { + $array = [ + 'string_value' => 'hello', + 'numeric_value' => 123, + 'array_value' => [ + 'name' => 'test', + // missing 'type' key + ], + 'another_array' => [ + 'name' => 'test', + 'type' => 'example' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'test', 'type'); + + $this->assertCount(1, $result); + $this->assertEquals($array['array_value'], $result[0]['content']); + $this->assertEquals('array_value', $result[0]['path']); + $this->assertEquals(['type'], $result[0]['missing_key']); + } + + /** + * Test path building with deeper nesting + */ + public function testDeepNestingPathBuilding() + { + $array = [ + 'level1' => [ + 'level2' => [ + 'level3' => [ + 'items' => [ + 'target_item' => [ + 'name' => 'deep_test', + // missing 'required_field' + ] + ] + ] + ] + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'deep_test', 'required_field'); + + $this->assertCount(1, $result); + $this->assertEquals('level1:level2:level3:items:target_item', $result[0]['path']); + } + + /** + * Test with custom path separator + */ + public function testCustomPathSeparator() + { + $array = [ + 'level1' => [ + 'level2' => [ + 'item' => [ + 'name' => 'test', + // missing 'type' key + ] + ] + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'test', 'type', null, '/'); + + $this->assertCount(1, $result); + $this->assertEquals('level1/level2/item', $result[0]['path']); + } + + /** + * Test default path separator behavior + */ + public function testDefaultPathSeparator() + { + $array = [ + 'parent' => [ + 'child' => [ + 'name' => 'test', + // missing 'value' key + ] + ] + ]; + + // Using default separator (should be ':') + $result = ArrayHandler::findArraysMissingKey($array, 'test', 'value'); + + $this->assertCount(1, $result); + $this->assertEquals('parent:child', $result[0]['path']); + } + + /** + * Test different path separators don't affect search logic + */ + public function testPathSeparatorDoesNotAffectSearchLogic() + { + $array = [ + 'section' => [ + 'data' => [ + 'id' => 123, + 'name' => 'item' + // missing 'status' + ] + ] + ]; + + // Test with different separators - results should be identical except for path + $result1 = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id', ':'); + $result2 = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id', '.'); + $result3 = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id', '/'); + + $this->assertCount(1, $result1); + $this->assertCount(1, $result2); + $this->assertCount(1, $result3); + + // Content and missing_key should be the same + $this->assertEquals($result1[0]['content'], $result2[0]['content']); + $this->assertEquals($result1[0]['content'], $result3[0]['content']); + $this->assertEquals($result1[0]['missing_key'], $result2[0]['missing_key']); + $this->assertEquals($result1[0]['missing_key'], $result3[0]['missing_key']); + + // Paths should be different based on separator + $this->assertEquals('section:data', $result1[0]['path']); + $this->assertEquals('section.data', $result2[0]['path']); + $this->assertEquals('section/data', $result3[0]['path']); + } + + /** + * test type checking + */ + public function testStrictTypeChecking() + { + $array = [ + 'item1' => [ + 'id' => '123', // string + 'name' => 'test' + // missing 'status' + ], + 'item2' => [ + 'id' => 123, // integer + 'name' => 'test2', + 'status' => 'active' + ] + ]; + + // Search for integer 123 - should only match item2 + $result = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id'); + $this->assertEmpty($result); // item2 has the status key + + // Search for string '123' - should only match item1 + $result = ArrayHandler::findArraysMissingKey($array, '123', 'status', 'id'); + $this->assertCount(1, $result); + $this->assertEquals('item1', $result[0]['path']); + } +} + +// __END__ diff --git a/test/phpunit/Combined/CoreLibsCombinedArrayHandlerKsortArrayTest.php b/test/phpunit/Combined/CoreLibsCombinedArrayHandlerKsortArrayTest.php new file mode 100644 index 0000000..bd152d8 --- /dev/null +++ b/test/phpunit/Combined/CoreLibsCombinedArrayHandlerKsortArrayTest.php @@ -0,0 +1,333 @@ + 'value1', + 'apple' => 'value2', + 'banana' => 'value3', + 'cherry' => 'value4' + ]; + + $expected = [ + 'apple' => 'value2', + 'banana' => 'value3', + 'cherry' => 'value4', + 'zebra' => 'value1' + ]; + + $result = ArrayHandler::ksortArray($input); + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test descending sort with reverse=true + */ + public function testKsortArrayDescending(): void + { + $input = [ + 'zebra' => 'value1', + 'apple' => 'value2', + 'banana' => 'value3', + 'cherry' => 'value4' + ]; + + $expected = [ + 'zebra' => 'value1', + 'cherry' => 'value4', + 'banana' => 'value3', + 'apple' => 'value2' + ]; + + $result = ArrayHandler::ksortArray($input, false, true); + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test case-insensitive ascending sort + */ + public function testKsortArrayCaseInsensitiveAscending(): void + { + $input = [ + 'Zebra' => 'value1', + 'apple' => 'value2', + 'Banana' => 'value3', + 'cherry' => 'value4' + ]; + + $expected = [ + 'apple' => 'value2', + 'Banana' => 'value3', + 'cherry' => 'value4', + 'Zebra' => 'value1' + ]; + + $result = ArrayHandler::ksortArray($input, true); + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test case-insensitive descending sort + */ + public function testKsortArrayCaseInsensitiveDescending(): void + { + $input = [ + 'Zebra' => 'value1', + 'apple' => 'value2', + 'Banana' => 'value3', + 'cherry' => 'value4' + ]; + + $expected = [ + 'Zebra' => 'value1', + 'cherry' => 'value4', + 'Banana' => 'value3', + 'apple' => 'value2' + ]; + + $result = ArrayHandler::ksortArray($input, true, true); + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test with mixed case keys to verify case sensitivity behavior + */ + public function testKsortArrayCaseSensitivityComparison(): void + { + $input = [ + 'B' => 'value1', + 'a' => 'value2', + 'C' => 'value3', + 'b' => 'value4' + ]; + + // Case-sensitive sort (uppercase comes before lowercase in ASCII) + $expectedCaseSensitive = [ + 'B' => 'value1', + 'C' => 'value3', + 'a' => 'value2', + 'b' => 'value4' + ]; + + // Case-insensitive sort + $expectedCaseInsensitive = [ + 'a' => 'value2', + 'B' => 'value1', + 'b' => 'value4', + 'C' => 'value3' + ]; + + $resultCaseSensitive = ArrayHandler::ksortArray($input, false); + $resultCaseInsensitive = ArrayHandler::ksortArray($input, true); + + $this->assertEquals($expectedCaseSensitive, $resultCaseSensitive); + $this->assertEquals($expectedCaseInsensitive, $resultCaseInsensitive); + } + + /** + * Test with numeric string keys + */ + public function testKsortArrayNumericStringKeys(): void + { + $input = [ + '10' => 'value1', + '2' => 'value2', + '1' => 'value3', + '20' => 'value4' + ]; + + // String comparison, not numeric + $expected = [ + '1' => 'value3', + '10' => 'value1', + '2' => 'value2', + '20' => 'value4' + ]; + + $result = ArrayHandler::ksortArray($input); + $this->assertEquals($expected, $result); + } + + /** + * Test with special characters in keys + */ + public function testKsortArraySpecialCharacters(): void + { + $input = [ + 'key_with_underscore' => 'value1', + 'key-with-dash' => 'value2', + 'key.with.dot' => 'value3', + 'key with space' => 'value4', + 'keyWithCamelCase' => 'value5' + ]; + + $result = ArrayHandler::ksortArray($input); + + // Verify it doesn't throw an error and maintains all keys + $this->assertCount(5, $result); + $this->assertArrayHasKey('key_with_underscore', $result); + $this->assertArrayHasKey('key-with-dash', $result); + $this->assertArrayHasKey('key.with.dot', $result); + $this->assertArrayHasKey('key with space', $result); + $this->assertArrayHasKey('keyWithCamelCase', $result); + } + + /** + * Test with empty array + */ + public function testKsortArrayEmpty(): void + { + $input = []; + $result = ArrayHandler::ksortArray($input); + $this->assertEquals([], $result); + $this->assertIsArray($result); + } + + /** + * Test with single element array + */ + public function testKsortArraySingleElement(): void + { + $input = ['onlykey' => 'onlyvalue']; + $result = ArrayHandler::ksortArray($input); + $this->assertEquals($input, $result); + } + + /** + * Test that original array is not modified (function returns new array) + */ + public function testKsortArrayDoesNotModifyOriginal(): void + { + $original = [ + 'zebra' => 'value1', + 'apple' => 'value2', + 'banana' => 'value3' + ]; + + $originalCopy = $original; // Keep a copy for comparison + $result = ArrayHandler::ksortArray($original); + + // Original array should remain unchanged + $this->assertEquals($originalCopy, $original); + $this->assertNotEquals(array_keys($original), array_keys($result)); + } + + /** + * Test with complex mixed data types as values + */ + public function testKsortArrayMixedValueTypes(): void + { + $input = [ + 'string_key' => 'string_value', + 'array_key' => ['nested', 'array'], + 'int_key' => 42, + 'bool_key' => true, + 'null_key' => null + ]; + + $result = ArrayHandler::ksortArray($input); + + // Check that all keys are preserved and sorted + $expectedKeys = ['array_key', 'bool_key', 'int_key', 'null_key', 'string_key']; + $this->assertEquals($expectedKeys, array_keys($result)); + + // Check that values are preserved correctly + $this->assertEquals('string_value', $result['string_key']); + $this->assertEquals(['nested', 'array'], $result['array_key']); + $this->assertEquals(42, $result['int_key']); + $this->assertTrue($result['bool_key']); + $this->assertNull($result['null_key']); + } + + /** + * Test all parameter combinations + */ + public function testKsortArrayAllParameterCombinations(): void + { + $input = [ + 'Delta' => 'value1', + 'alpha' => 'value2', + 'Charlie' => 'value3', + 'bravo' => 'value4' + ]; + + // Test all 4 combinations + $result1 = ArrayHandler::ksortArray($input, false, false); // default + $result2 = ArrayHandler::ksortArray($input, false, true); // reverse only + $result3 = ArrayHandler::ksortArray($input, true, false); // lowercase only + $result4 = ArrayHandler::ksortArray($input, true, true); // both + + // Each should produce different ordering + $this->assertNotEquals(array_keys($result1), array_keys($result2)); + $this->assertNotEquals(array_keys($result1), array_keys($result3)); + $this->assertNotEquals(array_keys($result1), array_keys($result4)); + $this->assertNotEquals(array_keys($result2), array_keys($result3)); + $this->assertNotEquals(array_keys($result2), array_keys($result4)); + $this->assertNotEquals(array_keys($result3), array_keys($result4)); + + // But all should have same keys and values, just different order + $this->assertEqualsCanonicalizing(array_values($input), array_values($result1)); + $this->assertEqualsCanonicalizing(array_values($input), array_values($result2)); + $this->assertEqualsCanonicalizing(array_values($input), array_values($result3)); + $this->assertEqualsCanonicalizing(array_values($input), array_values($result4)); + } + + /** + * Data provider for comprehensive testing + */ + public function sortingParametersProvider(): array + { + return [ + 'default' => [false, false], + 'reverse' => [false, true], + 'lowercase' => [true, false], + 'lowercase_reverse' => [true, true], + ]; + } + + /** + * Test that function works with all parameter combinations using data provider + * + * @dataProvider sortingParametersProvider + */ + public function testKsortArrayWithDataProvider(bool $lowerCase, bool $reverse): void + { + $input = [ + 'Zebra' => 'animal1', + 'apple' => 'fruit1', + 'Banana' => 'fruit2', + 'cat' => 'animal2' + ]; + + $result = ArrayHandler::ksortArray($input, $lowerCase, $reverse); + + // Basic assertions that apply to all combinations + $this->assertIsArray($result); + $this->assertCount(4, $result); + $this->assertArrayHasKey('Zebra', $result); + $this->assertArrayHasKey('apple', $result); + $this->assertArrayHasKey('Banana', $result); + $this->assertArrayHasKey('cat', $result); + } +} + +// __END__ diff --git a/test/phpunit/Combined/CoreLibsCombinedArrayHandlerSelectArrayFromOptionTest.php b/test/phpunit/Combined/CoreLibsCombinedArrayHandlerSelectArrayFromOptionTest.php new file mode 100644 index 0000000..193f170 --- /dev/null +++ b/test/phpunit/Combined/CoreLibsCombinedArrayHandlerSelectArrayFromOptionTest.php @@ -0,0 +1,383 @@ +testData = [ + 'item1' => [ + 'name' => 'John', + 'age' => 25, + 'status' => 'active', + 'score' => 85.5 + ], + 'item2' => [ + 'name' => 'jane', + 'age' => 30, + 'status' => 'inactive', + 'score' => 92.0 + ], + 'item3' => [ + 'name' => 'Bob', + 'age' => 25, + 'status' => 'active', + 'score' => 78.3 + ], + 'item4' => [ + 'name' => 'Alice', + 'age' => 35, + 'status' => 'pending', + 'score' => 88.7 + ] + ]; + + $this->nestedTestData = [ + 'level1_a' => [ + 'name' => 'Level1A', + 'type' => 'parent', + 'children' => [ + 'child1' => [ + 'name' => 'Child1', + 'type' => 'child', + 'active' => true + ], + 'child2' => [ + 'name' => 'Child2', + 'type' => 'child', + 'active' => false + ] + ] + ], + 'level1_b' => [ + 'name' => 'Level1B', + 'type' => 'parent', + 'children' => [ + 'child3' => [ + 'name' => 'Child3', + 'type' => 'child', + 'active' => true, + 'nested' => [ + 'deep1' => [ + 'name' => 'Deep1', + 'type' => 'deep', + 'active' => true + ] + ] + ] + ] + ], + 'item5' => [ + 'name' => 'Direct', + 'type' => 'child', + 'active' => false + ] + ]; + } + + public function testEmptyArrayReturnsEmpty(): void + { + $result = ArrayHandler::selectArrayFromOption([], 'name', 'John'); + $this->assertEmpty($result); + } + + public function testBasicStringSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'John'); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertEquals('John', $result['item1']['name']); + } + + public function testBasicIntegerSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'age', 25); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertArrayHasKey('item3', $result); + } + + public function testBasicFloatSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'score', 85.5); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertEquals(85.5, $result['item1']['score']); + } + + public function testBasicBooleanSearch(): void + { + $data = [ + 'item1' => ['enabled' => true, 'name' => 'Test1'], + 'item2' => ['enabled' => false, 'name' => 'Test2'], + 'item3' => ['enabled' => true, 'name' => 'Test3'] + ]; + + $result = ArrayHandler::selectArrayFromOption($data, 'enabled', true); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertArrayHasKey('item3', $result); + } + + public function testStrictComparison(): void + { + $data = [ + 'item1' => ['value' => '25', 'name' => 'String25'], + 'item2' => ['value' => 25, 'name' => 'Int25'], + 'item3' => ['value' => 25.0, 'name' => 'Float25'] + ]; + + // Non-strict should match all + $nonStrictResult = ArrayHandler::selectArrayFromOption($data, 'value', 25, false); + $this->assertCount(3, $nonStrictResult); + + // Strict should only match exact type + $strictResult = ArrayHandler::selectArrayFromOption($data, 'value', 25, true); + $this->assertCount(1, $strictResult); + $this->assertArrayHasKey('item2', $strictResult); + } + + public function testCaseInsensitiveSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'JANE', false, true); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('item2', $result); + $this->assertEquals('jane', $result['item2']['name']); + } + + public function testCaseSensitiveSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'JANE', false, false); + + $this->assertEmpty($result); + } + + public function testRecursiveSearchWithFlatResult(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'child', + false, + false, + true, + true, + ':*' + ); + + $this->assertCount(4, $result); + $this->assertArrayHasKey('level1_a:*children:*child1', $result); + $this->assertArrayHasKey('level1_a:*children:*child2', $result); + $this->assertArrayHasKey('level1_b:*children:*child3', $result); + $this->assertArrayHasKey('item5', $result); + } + + public function testRecursiveSearchWithNestedResult(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'child', + false, + false, + true, + false + ); + + $this->assertCount(3, $result); + $this->assertArrayHasKey('level1_a', $result); + $this->assertArrayHasKey('level1_b', $result); + $this->assertArrayHasKey('item5', $result); + + // Check nested structure is preserved + $this->assertArrayHasKey('children', $result['level1_a']); + $this->assertArrayHasKey('child1', $result['level1_a']['children']); + $this->assertArrayHasKey('child2', $result['level1_a']['children']); + } + + public function testRecursiveSearchDeepNesting(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'deep', + false, + false, + true, + true, + ':*' + ); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('level1_b:*children:*child3:*nested:*deep1', $result); + $this->assertEquals('Deep1', $result['level1_b:*children:*child3:*nested:*deep1']['name']); + } + + public function testCustomFlatSeparator(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'child', + false, + false, + true, + true, + '|' + ); + + $this->assertArrayHasKey('level1_a|children|child1', $result); + $this->assertArrayHasKey('level1_a|children|child2', $result); + $this->assertArrayHasKey('level1_b|children|child3', $result); + } + + public function testNonRecursiveSearch(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'child', + false, + false, + false + ); + + // Should only find direct matches, not nested ones + $this->assertCount(1, $result); + $this->assertArrayHasKey('item5', $result); + } + + public function testNoMatchesFound(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'NonExistent'); + + $this->assertEmpty($result); + } + + public function testMissingLookupKey(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'nonexistent_key', 'value'); + + $this->assertEmpty($result); + } + + public function testCombinedStrictAndCaseInsensitive(): void + { + $data = [ + 'item1' => ['name' => 'Test', 'id' => '123'], + 'item2' => ['name' => 'test', 'id' => 123], + 'item3' => ['name' => 'TEST', 'id' => '123'] + ]; + + // Case insensitive but strict type matching + $result = ArrayHandler::selectArrayFromOption($data, 'id', '123', true, true); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertArrayHasKey('item3', $result); + } + + public function testBooleanWithCaseInsensitive(): void + { + $data = [ + 'item1' => ['active' => true, 'name' => 'Test1'], + 'item2' => ['active' => false, 'name' => 'Test2'] + ]; + + // Case insensitive flag should not affect boolean comparison + $result = ArrayHandler::selectArrayFromOption($data, 'active', true, false, true); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('item1', $result); + } + + public function testArrayWithNumericKeys(): void + { + $data = [ + 0 => ['name' => 'First', 'type' => 'test'], + 1 => ['name' => 'Second', 'type' => 'test'], + 2 => ['name' => 'Third', 'type' => 'other'] + ]; + + $result = ArrayHandler::selectArrayFromOption($data, 'type', 'test'); + + $this->assertCount(2, $result); + $this->assertArrayHasKey(0, $result); + $this->assertArrayHasKey(1, $result); + } + + public function testRecursiveWithMixedKeyTypes(): void + { + $data = [ + 'string_key' => [ + 'name' => 'Parent', + 'type' => 'parent', + 0 => [ + 'name' => 'Child0', + 'type' => 'child' + ], + 'child_key' => [ + 'name' => 'ChildKey', + 'type' => 'child' + ] + ] + ]; + + $result = ArrayHandler::selectArrayFromOption($data, 'type', 'child', false, false, true, true, ':*'); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('string_key:*0', $result); + $this->assertArrayHasKey('string_key:*child_key', $result); + } + + public function testAllParametersCombined(): void + { + $data = [ + 'parent1' => [ + 'name' => 'Parent1', + 'status' => 'ACTIVE', + 'children' => [ + 'child1' => [ + 'name' => 'Child1', + 'status' => 'active' + ] + ] + ] + ]; + + $result = ArrayHandler::selectArrayFromOption( + $data, + 'status', + 'active', + false, // not strict + true, // case insensitive + true, // recursive + true, // flat result + '|' // custom separator + ); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('parent1', $result); + $this->assertArrayHasKey('parent1|children|child1', $result); + } +} + +// __END__ diff --git a/test/phpunit/Combined/CoreLibsCombinedArrayHandlerSortArrayTest.php b/test/phpunit/Combined/CoreLibsCombinedArrayHandlerSortArrayTest.php new file mode 100644 index 0000000..9d0aec8 --- /dev/null +++ b/test/phpunit/Combined/CoreLibsCombinedArrayHandlerSortArrayTest.php @@ -0,0 +1,328 @@ +assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test basic descending sort without maintaining keys + */ + public function testBasicDescendingSort() + { + $input = [3, 1, 4, 1, 5, 9]; + $expected = [9, 5, 4, 3, 1, 1]; + + $result = ArrayHandler::sortArray($input, false, true); + + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test ascending sort with key maintenance + */ + public function testAscendingSortWithKeyMaintenance() + { + $input = ['c' => 3, 'a' => 1, 'd' => 4, 'b' => 1, 'e' => 5]; + $expected = ['a' => 1, 'b' => 1, 'c' => 3, 'd' => 4, 'e' => 5]; + + $result = ArrayHandler::sortArray($input, false, false, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test descending sort with key maintenance + */ + public function testDescendingSortWithKeyMaintenance() + { + $input = ['c' => 3, 'a' => 1, 'd' => 4, 'b' => 1, 'e' => 5]; + $expected = ['e' => 5, 'd' => 4, 'c' => 3, 'a' => 1, 'b' => 1]; + + $result = ArrayHandler::sortArray($input, false, true, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test string sorting with lowercase conversion + */ + public function testStringLowerCaseSort() + { + $input = ['Banana', 'apple', 'Cherry', 'date']; + $expected = ['apple', 'Banana', 'Cherry', 'date']; + + $result = ArrayHandler::sortArray($input, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test string sorting with lowercase conversion in reverse + */ + public function testStringLowerCaseSortReverse() + { + $input = ['Banana', 'apple', 'Cherry', 'date']; + $expected = ['date', 'Cherry', 'Banana', 'apple']; + + $result = ArrayHandler::sortArray($input, true, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test string sorting with lowercase conversion and key maintenance + */ + public function testStringLowerCaseSortWithKeys() + { + $input = ['b' => 'Banana', 'a' => 'apple', 'c' => 'Cherry', 'd' => 'date']; + $expected = ['a' => 'apple', 'b' => 'Banana', 'c' => 'Cherry', 'd' => 'date']; + + $result = ArrayHandler::sortArray($input, true, false, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test string sorting with lowercase conversion, reverse, and key maintenance + */ + public function testStringLowerCaseSortReverseWithKeys() + { + $input = ['b' => 'Banana', 'a' => 'apple', 'c' => 'Cherry', 'd' => 'date']; + $expected = ['d' => 'date', 'c' => 'Cherry', 'b' => 'Banana', 'a' => 'apple']; + + $result = ArrayHandler::sortArray($input, true, true, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test numeric string sorting with SORT_NUMERIC flag + */ + public function testNumericStringSorting() + { + $input = ['10', '2', '1', '20']; + $expected = ['1', '2', '10', '20']; + + $result = ArrayHandler::sortArray($input, false, false, false, SORT_NUMERIC); + + $this->assertEquals($expected, $result); + } + + /** + * Test natural string sorting with SORT_NATURAL flag + */ + public function testNaturalStringSorting() + { + $input = ['img1.png', 'img10.png', 'img2.png', 'img20.png']; + $expected = ['img1.png', 'img2.png', 'img10.png', 'img20.png']; + + $result = ArrayHandler::sortArray($input, false, false, false, SORT_NATURAL); + + $this->assertEquals($expected, $result); + } + + /** + * Test with empty array + */ + public function testEmptyArray() + { + $input = []; + $expected = []; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($expected, $result); + } + + /** + * Test with single element array + */ + public function testSingleElementArray() + { + $input = [42]; + $expected = [42]; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($expected, $result); + } + + /** + * Test with array containing null values + */ + public function testArrayWithNullValues() + { + $input = [3, null, 1, null, 2]; + $expected = [null, null, 1, 2, 3]; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($expected, $result); + } + + /** + * Test with mixed data types + */ + public function testMixedDataTypes() + { + $input = [3, '1', 4.5, '2', 1]; + + $result = ArrayHandler::sortArray($input); + + // Should sort according to PHP's natural comparison rules + $this->assertIsArray($result); + $this->assertCount(5, $result); + } + + /** + * Test that original array is not modified (immutability) + */ + public function testOriginalArrayNotModified() + { + $original = [3, 1, 4, 1, 5, 9]; + $input = $original; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($original, $input); + $this->assertNotEquals($input, $result); + } + + /** + * Test case sensitivity without lowercase flag + */ + public function testCaseSensitivityWithoutLowercase() + { + $input = ['Banana', 'apple', 'Cherry']; + + $result = ArrayHandler::sortArray($input); + + // Capital letters should come before lowercase in ASCII sort + $this->assertEquals('Banana', $result[0]); + $this->assertEquals('Cherry', $result[1]); + $this->assertEquals('apple', $result[2]); + } + + /** + * Test all parameters combination + */ + public function testAllParametersCombination() + { + $input = ['z' => 'Zebra', 'a' => 'apple', 'b' => 'Banana']; + + $result = ArrayHandler::sortArray($input, true, true, true, SORT_REGULAR); + + // Should be sorted by lowercase, reversed, with keys maintained + $keys = array_keys($result); + $values = array_values($result); + + $this->assertEquals(['z', 'b', 'a'], $keys); + $this->assertEquals(['Zebra', 'Banana', 'apple'], $values); + } + + /** + * Test floating point numbers + */ + public function testFloatingPointNumbers() + { + $input = [3.14, 2.71, 1.41, 1.73]; + $expected = [1.41, 1.73, 2.71, 3.14]; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($expected, $result); + } + + /** + * Test with duplicate values and key maintenance + */ + public function testDuplicateValuesWithKeyMaintenance() + { + $input = ['first' => 1, 'second' => 2, 'third' => 1, 'fourth' => 2]; + + $result = ArrayHandler::sortArray($input, false, false, true); + + $this->assertCount(4, $result); + $this->assertEquals([1, 1, 2, 2], array_values($result)); + // Keys should be preserved + $this->assertArrayHasKey('first', $result); + $this->assertArrayHasKey('second', $result); + $this->assertArrayHasKey('third', $result); + $this->assertArrayHasKey('fourth', $result); + } + + /** + * Data provider for comprehensive parameter testing + */ + public function sortParameterProvider(): array + { + return [ + 'basic_ascending' => [ + [3, 1, 4, 2], + false, false, false, SORT_REGULAR, + [1, 2, 3, 4] + ], + 'basic_descending' => [ + [3, 1, 4, 2], + false, true, false, SORT_REGULAR, + [4, 3, 2, 1] + ], + 'lowercase_ascending' => [ + ['Banana', 'apple', 'Cherry'], + true, false, false, SORT_REGULAR, + ['apple', 'Banana', 'Cherry'] + ], + 'lowercase_descending' => [ + ['Banana', 'apple', 'Cherry'], + true, true, false, SORT_REGULAR, + ['Cherry', 'Banana', 'apple'] + ] + ]; + } + + /** + * Test various parameter combinations using data provider + * + * @dataProvider sortParameterProvider + */ + public function testSortParameterCombinations( + array $input, + bool $lowercase, + bool $reverse, + bool $maintainKeys, + int $params, + array $expected + ) { + $result = ArrayHandler::sortArray($input, $lowercase, $reverse, $maintainKeys, $params); + + if (!$maintainKeys) { + $this->assertEquals($expected, $result); + } else { + $this->assertEquals($expected, array_values($result)); + } + } +} + +// __END__ diff --git a/test/phpunit/Combined/CoreLibsCombinedArrayHandlerTest.php b/test/phpunit/Combined/CoreLibsCombinedArrayHandlerTest.php index e11aab8..8843393 100644 --- a/test/phpunit/Combined/CoreLibsCombinedArrayHandlerTest.php +++ b/test/phpunit/Combined/CoreLibsCombinedArrayHandlerTest.php @@ -7,7 +7,6 @@ declare(strict_types=1); namespace tests; -use Exception; use PHPUnit\Framework\TestCase; /** @@ -25,7 +24,11 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase 'same' => 'same', 3 => 'foobar', 'foobar' => 4, + 'barbaz' => 5, + 'zapzap' => '6', + 'zipzip' => 7, 'true' => true, + 'more' => 'other', ], 'd', 4, @@ -37,7 +40,10 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase 'sub' => [ 'nested' => 'bar', 'same' => 'same', - 'more' => 'test' + 'more' => 'test', + 'barbaz' => '5', + 'zapzap' => '6', + 'zipzip' => 7, ] ] ]; @@ -184,7 +190,7 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase 0: array $input, 1: $key, 2: $value, - 3: bool $flag, + 3: bool $strict, 4: bool $expected */ return [ @@ -286,6 +292,91 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase 3 => true, 4 => false, ], + // array tyep search + 'array type, both exist' => [ + 0 => self::$array, + 1 => 'more', + 2 => ['other', 'test'], + 3 => false, + 4 => true, + ], + 'array type, one exist' => [ + 0 => self::$array, + 1 => 'more', + 2 => ['other', 'not'], + 3 => false, + 4 => true, + ], + 'array type, none exist' => [ + 0 => self::$array, + 1 => 'more', + 2 => ['never', 'not'], + 3 => false, + 4 => false, + ], + 'array type, both exist, not strict, int and string' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => [5, '5'], + 3 => false, + 4 => true, + ], + 'array type, both exist, not strict, both string' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => ['5', '5'], + 3 => false, + 4 => true, + ], + 'array type, both exist, not strict, int and int' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => [5, 5], + 3 => false, + 4 => true, + ], + 'array type, both exist, strict, int and string' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => [5, '5'], + 3 => true, + 4 => true, + ], + 'array type, both exist, strict, both string' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => ['5', '5'], + 3 => true, + 4 => true, + ], + 'array type, both exist, strict, int and int' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => [5, 5], + 3 => true, + 4 => true, + ], + 'array type, both exist, strict, int and int to string and string' => [ + 0 => self::$array, + 1 => 'zapzap', + 2 => [6, 6], + 3 => true, + 4 => false, + ], + 'array type, both exist, strict, string and string to string and string' => [ + 0 => self::$array, + 1 => 'zapzap', + 2 => ['6', '6'], + 3 => true, + 4 => true, + ], + 'array type, both exist, not strict, int and int to string and string' => [ + 0 => self::$array, + 1 => 'zapzap', + 2 => [6, 6], + 3 => false, + 4 => true, + ], ]; } @@ -842,13 +933,13 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase * @dataProvider arraySearchRecursiveAllProvider * @testdox arraySearchRecursiveAll $needle (key $key_search_for) in $input and will be $expected (old: $flag) [$_dataName] * - * @param string|null $needle + * @param string|int|null $needle * @param array $input - * @param string|null $key_search_for + * @param string|int|null $key_search_for * @param bool $flag * @return void */ - public function testArraySearchRecursiveAll($needle, array $input, ?string $key_search_for, bool $flag, array $expected): void + public function testArraySearchRecursiveAll(string|int|null $needle, array $input, string|int|null $key_search_for, bool $flag, array $expected): void { $this->assertEquals( $expected, @@ -865,15 +956,16 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase * * @param array $input * @param string|int $key - * @param string|int|bool $value + * @param string|int|bool|array $value + * @param bool $strict * @param bool $expected * @return void */ - public function testArraySearchSimple(array $input, $key, $value, bool $flag, bool $expected): void + public function testArraySearchSimple(array $input, string|int $key, string|int|bool|array $value, bool $strict, bool $expected): void { $this->assertEquals( $expected, - \CoreLibs\Combined\ArrayHandler::arraySearchSimple($input, $key, $value, $flag) + \CoreLibs\Combined\ArrayHandler::arraySearchSimple($input, $key, $value, $strict) ); } @@ -1394,8 +1486,896 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase array $expected ): void { $this->assertEquals( - \CoreLibs\Combined\ArrayHandler::arrayModifyKey($in_array, $key_mod_prefix, $key_mod_suffix), - $expected + $expected, + \CoreLibs\Combined\ArrayHandler::arrayModifyKey($in_array, $key_mod_prefix, $key_mod_suffix) + ); + } + + /** + * sort + * + * @return array + */ + public function providerSortArray(): array + { + $unsorted = [9, 5, 'A', 4, 'B', 6, 'c', 'C', 'a']; + // for lower case the initial order of the elmenet is important: + // A, a => A, a + // d, D => d, D + $unsorted_keys = ['A' => 9, 'B' => 5, 'C' => 'A', 'D' => 4, 'E' => 'B', 'F' => 6, 'G' => 'c', 'H' => 'C', 'I' => 'a']; + return [ + // sort default + 'sort default' => [ + $unsorted, + null, + null, + null, + [4, 5, 6, 9, 'A', 'B', 'C', 'a', 'c'], + ], + // sort param set + 'sort param set' => [ + $unsorted, + false, + false, + false, + [4, 5, 6, 9, 'A', 'B', 'C', 'a', 'c'], + ], + // sort lower case + 'sort lower case' => [ + $unsorted, + true, + false, + false, + [4, 5, 6, 9, 'A', 'a', 'B', 'c', 'C'], + ], + // sort reverse + 'sort reverse' => [ + $unsorted, + false, + true, + false, + ['c', 'a', 'C', 'B', 'A', 9, 6, 5, 4], + ], + // sort lower case + reverse + 'sort lower case + reverse' => [ + $unsorted, + true, + true, + false, + ['c', 'C', 'B', 'A', 'a', 9, 6, 5, 4], + ], + // keys, do not maintain, default + 'keys, do not maintain, default' => [ + $unsorted_keys, + false, + false, + false, + [4, 5, 6, 9, 'A', 'B', 'C', 'a', 'c'], + ], + // sort maintain keys + 'sort maintain keys' => [ + $unsorted_keys, + false, + false, + true, + [ + 'D' => 4, + 'B' => 5, + 'F' => 6, + 'A' => 9, + 'C' => 'A', + 'E' => 'B', + 'H' => 'C', + 'I' => 'a', + 'G' => 'c' + ], + ], + // sort maintain keys + lower case + 'sort maintain keys + lower case' => [ + $unsorted_keys, + true, + false, + true, + [ + 'D' => 4, + 'B' => 5, + 'F' => 6, + 'A' => 9, + 'C' => 'A', + 'I' => 'a', + 'E' => 'B', + 'H' => 'C', + 'G' => 'c' + ], + ], + // sort maintain keys + reverse + 'sort maintain keys + reverse' => [ + $unsorted_keys, + false, + true, + true, + [ + 'G' => 'c', + 'H' => 'C', + 'E' => 'B', + 'I' => 'a', + 'C' => 'A', + 'A' => 9, + 'F' => 6, + 'B' => 5, + 'D' => 4, + ], + ], + // sort maintain keys + lower case + reverse + 'sort maintain keys + lower case + reverse' => [ + $unsorted_keys, + true, + true, + true, + [ + 'G' => 'c', + 'H' => 'C', + 'E' => 'B', + 'I' => 'a', + 'C' => 'A', + 'A' => 9, + 'F' => 6, + 'B' => 5, + 'D' => 4, + ], + ], + // emtpy + 'empty' => [ + [], + false, + false, + false, + [] + ], + // with nulls + 'null entries' => [ + ['d', null, 'a', null, 1], + false, + false, + false, + [null, null, 1, 'a', 'd'], + ], + // double entries + 'double entries' => [ + [1, 2, 2, 1, 'B', 'A', 'a', 'b', 'A', 'B', 'b', 'a'], + false, + false, + false, + [1, 1, 2, 2, 'A', 'A', 'B', 'B', 'a', 'a', 'b', 'b'], + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::sortArray + * @dataProvider providerSortArray + * @testdox sortArray sort $input with lower case $lower_case, reverse $reverse, maintain keys $maintain_keys with expeted $expected [$_dataName] + * + * @param array $input + * @param ?bool $lower_case + * @param ?bool $reverse + * @param ?bool $maintain_keys + * @param array $expected + * @return void + */ + public function testSortArray(array $input, ?bool $lower_case, ?bool $reverse, ?bool $maintain_keys, array $expected): void + { + $original = $input; + if ($lower_case === null && $reverse === null && $maintain_keys === null) { + $sorted_array = \CoreLibs\Combined\ArrayHandler::sortArray($input); + } else { + $sorted_array = \CoreLibs\Combined\ArrayHandler::sortArray($input, $lower_case, $reverse, $maintain_keys); + } + $expected_count = count($expected); + $this->assertIsArray( + $sorted_array, + 'sortArray: result not array' + ); + $this->assertCount( + $expected_count, + $sorted_array, + 'sortArray: count not matching' + ); + $this->assertEquals( + $expected, + $sorted_array, + 'sortArray: result not matching' + ); + $this->assertEquals( + $original, + $input, + 'sortArray: original - input was modified' + ); + if ($maintain_keys) { + $this->assertEqualsCanonicalizing( + array_keys($input), + array_keys($sorted_array), + 'sortArray: keys are not modified', + ); + } + if ($input != []) { + // we only care about array values + $this->assertNotEquals( + array_values($input), + array_values($sorted_array), + 'sortArray: output - input was modified' + ); + } + } + +/** + * sort + * + * @return array + */ + public function providerKsortArray(): array + { + // for lower case the initial order of the elmenet is important: + // A, a => A, a + // d, D => d, D + $unsorted_keys = [ + 9 => 'A', + 5 => 'B', + 'A' => 'C', + 4 => 'D', + 'B' => 'E', + 6 => 'F', + 'c' => 'G', + 'C' => 'H', + 'a' => 'I', + ]; + return [ + // sort keys + 'sort keys' => [ + $unsorted_keys, + false, + false, + [ + 4 => 'D', + 5 => 'B', + 6 => 'F', + 9 => 'A', + 'A' => 'C', + 'B' => 'E', + 'C' => 'H', + 'a' => 'I', + 'c' => 'G', + ], + ], + // sort keys + lower case + 'sort keys + lower case' => [ + $unsorted_keys, + true, + false, + [ + 4 => 'D', + 5 => 'B', + 6 => 'F', + 9 => 'A', + 'A' => 'C', + 'a' => 'I', + 'B' => 'E', + 'c' => 'G', + 'C' => 'H', + ], + ], + // sort keys + reverse + 'sort keys + reverse' => [ + $unsorted_keys, + false, + true, + [ + 'c' => 'G', + 'a' => 'I', + 'C' => 'H', + 'B' => 'E', + 'A' => 'C', + 9 => 'A', + 6 => 'F', + 5 => 'B', + 4 => 'D', + ], + ], + // sort keys + lower case + reverse + 'sort keys + lower case + reverse' => [ + $unsorted_keys, + true, + true, + [ + 'C' => 'H', + 'c' => 'G', + 'B' => 'E', + 'a' => 'I', + 'A' => 'C', + 9 => 'A', + 6 => 'F', + 5 => 'B', + 4 => 'D', + ], + ], + // emtpy + 'empty' => [ + [], + false, + false, + [] + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::ksortArray + * @dataProvider providerKsortArray + * @testdox ksortArray sort $input with lower case $lower_case, reverse $reverse with expeted $expected [$_dataName] + * + * @param array $input + * @param ?bool $lower_case + * @param ?bool $reverse + * @param array $expected + * @return void + */ + public function testKsortArray(array $input, ?bool $lower_case, ?bool $reverse, array $expected): void + { + $original = $input; + if ($lower_case === null && $reverse === null) { + $sorted_array = \CoreLibs\Combined\ArrayHandler::ksortArray($input); + } else { + $sorted_array = \CoreLibs\Combined\ArrayHandler::ksortArray($input, $lower_case, $reverse); + } + $expected_count = count($expected); + $this->assertIsArray( + $sorted_array, + 'ksortArray: result not array' + ); + $this->assertCount( + $expected_count, + $sorted_array, + 'ksortArray: count not matching' + ); + $this->assertEquals( + $expected, + $sorted_array, + 'ksortArray: result not matching' + ); + $this->assertEquals( + $original, + $input, + 'ksortArray: original - input was modified' + ); + $this->assertEqualsCanonicalizing( + array_values($original), + array_values($sorted_array), + 'ksortArray: values are not modified' + ); + if ($input != []) { + // we only care about array keys + $this->assertNotEquals( + array_keys($input), + array_keys($sorted_array), + 'sortArray: output - input was modified' + ); + } + } + + /** + * Undocumented function + * + * @return array + */ + public function providerFindArraysMissingKey(): array + { + $search_array = [ + 'table_lookup' => [ + 'match' => [ + ['param' => 'access_d_cd', 'data' => 'a_cd', 'time_validation' => 'on_load',], + ['param' => 'other_block', 'data' => 'b_cd'], + ['pflaume' => 'other_block', 'data' => 'c_cd'], + ['param' => 'third_block', 'data' => 'd_cd', 'time_validation' => 'cool'], + ['special' => 'other_block', 'data' => 'e_cd', 'time_validation' => 'other'], + ] + ] + ]; + return [ + 'find, no key set' => [ + $search_array, + 'other_block', + 'time_validation', + null, + null, + [ + [ + 'content' => [ + 'param' => 'other_block', + 'data' => 'b_cd', + ], + 'path' => 'table_lookup:match:1', + 'missing_key' => ['time_validation'], + ], + [ + 'content' => [ + 'data' => 'c_cd', + 'pflaume' => 'other_block', + ], + 'path' => 'table_lookup:match:2', + 'missing_key' => ['time_validation'], + ], + ] + ], + 'find, key set' => [ + $search_array, + 'other_block', + 'time_validation', + 'pflaume', + null, + [ + [ + 'content' => [ + 'data' => 'c_cd', + 'pflaume' => 'other_block', + ], + 'path' => 'table_lookup:match:2', + 'missing_key' => ['time_validation'], + ], + ] + ], + 'find, key set, different separator' => [ + $search_array, + 'other_block', + 'time_validation', + 'pflaume', + '#', + [ + [ + 'content' => [ + 'data' => 'c_cd', + 'pflaume' => 'other_block', + ], + 'path' => 'table_lookup#match#2', + 'missing_key' => ['time_validation'], + ], + ] + ], + 'find, key set, multiple check' => [ + $search_array, + 'other_block', + ['data', 'time_validation'], + 'pflaume', + null, + [ + [ + 'content' => [ + 'data' => 'c_cd', + 'pflaume' => 'other_block', + ], + 'path' => 'table_lookup:match:2', + 'missing_key' => ['time_validation'], + ], + ] + ], + 'has set' => [ + $search_array, + 'access_d_cd', + 'time_validation', + null, + null, + [] + ], + 'not found' => [ + $search_array, + 'not_found', + 'value', + null, + null, + [] + ], + 'empty' => [ + [], + 'something', + 'other', + null, + null, + [] + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::findArraysMissingKey + * @dataProvider providerFindArraysMissingKey + * @testdox findArraysMissingKey $input find $search_value with $search_key and missing $required_key [$_dataName] + * + * @param array $input + * @param string|int|float|bool $search_value + * @param string|array $required_key + * @param string|null $search_key + * @param string|null $path_separator + * @param array $expected + * @return void + */ + public function testFindArraysMissingKey( + array $input, + string|int|float|bool $search_value, + string|array $required_key, + ?string $search_key, + ?string $path_separator, + array $expected + ): void { + if ($path_separator === null) { + $result = \CoreLibs\Combined\ArrayHandler::findArraysMissingKey( + $input, + $search_value, + $required_key, + $search_key + ); + } else { + $result = \CoreLibs\Combined\ArrayHandler::findArraysMissingKey( + $input, + $search_value, + $required_key, + $search_key, + $path_separator + ); + } + $this->assertEquals( + $expected, + $result + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function providerSelectArrayFromOption(): array + { + $search_array = [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'c' => [ + 'lookup' => 0, + 'value' => 'CCC', + 'other' => 'OTHER', + ], + 'd' => [ + 'd-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'd-2' => [ + 'lookup' => 0, + 'value' => 'D SUB 2', + 'other' => 'Other B', + ], + 'more' => [ + 'd-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + 'd-more-2' => [ + 'lookup' => 0, + 'value' => 'D MORE SUB 0', + 'other' => 'Other C', + ], + ] + ] + ]; + /* + 0: input + 1: lookup + 2: search + 3: strict [false] + 4: case insensitive [false] + 5: recursive [false] + 6: flat_result [true] + 7: flat_separator [:] + 8: expected + */ + return [ + 'search, flat with found' => [ + $search_array, + 'lookup' => 'lookup', + 'search' => 1, + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + ] + ], + 'search, recusrive with found' => [ + $search_array, + 'lookup' => 'lookup', + 'search' => 1, + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => true, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'd:d-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'd:more:d-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + ] + ], + 'search, recusrive with found, other separator' => [ + $search_array, + 'lookup' => 'lookup', + 'search' => 1, + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => true, + 'flat_result' => true, + 'flag_separator' => '+', + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'd+d-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'd+more+d-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + ] + ], + 'search, recusrive with found, not flat result' => [ + $search_array, + 'lookup' => 'lookup', + 'search' => 1, + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => true, + 'flat_result' => false, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'd' => [ + 'd-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'more' => [ + 'd-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + ], + ], + ], + ], + 'search case insensitive' => [ + $search_array, + 'lookup' => 'other', + 'search' => 'Other', + 'strict' => false, + 'case_insenstivie' => true, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'c' => [ + 'lookup' => 0, + 'value' => 'CCC', + 'other' => 'OTHER', + ], + ] + ], + 'search case sensitiv' => [ + $search_array, + 'lookup' => 'other', + 'search' => 'Other', + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + ] + ], + 'search strict' => [ + $search_array, + 'lookup' => 'strict', + 'search' => '2', + 'strict' => true, + 'case_insenstivie' => false, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + ] + ], + 'search not strict' => [ + $search_array, + 'lookup' => 'strict', + 'search' => '2', + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + ] + ], + 'empty' => [ + [], + 'something', + 'NOT_SET_AT_ALL', + false, + false, + false, + true, + null, + [] + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::selectArrayFromOption + * @dataProvider providerSelectArrayFromOption + * @testdox selectArrayFromOption $input find $lookup with $search, strict: $strict, case sensitive: $case_sensitive, recursive: $recursive, flag result: $flag_result, flag separator: $flat_separator amd expected $expected [$_dataName] + * + * @param array $input + * @param string $lookup + * @param int|string|float|bool $search + * @param bool $strict + * @param bool $case_sensitive + * @param bool $recursive + * @param bool $flat_result + * @param string|null $flat_separator + * @param array $expected + * @return void + */ + public function testSelectArrayFromOption( + array $input, + string $lookup, + int|string|float|bool $search, + bool $strict, + bool $case_sensitive, + bool $recursive, + bool $flat_result, + ?string $flat_separator, + array $expected + ): void { + if ($flat_separator === null) { + $result = \CoreLibs\Combined\ArrayHandler::selectArrayFromOption( + $input, + $lookup, + $search, + $strict, + $case_sensitive, + $recursive, + $flat_result + ); + } else { + $result = \CoreLibs\Combined\ArrayHandler::selectArrayFromOption( + $input, + $lookup, + $search, + $strict, + $case_sensitive, + $recursive, + $flat_result, + $flat_separator + ); + } + $this->assertEquals( + $expected, + $result ); } } diff --git a/test/phpunit/Convert/CoreLibsConvertStringsRegexValidateTest.php b/test/phpunit/Convert/CoreLibsConvertStringsRegexValidateTest.php new file mode 100644 index 0000000..58dba0b --- /dev/null +++ b/test/phpunit/Convert/CoreLibsConvertStringsRegexValidateTest.php @@ -0,0 +1,283 @@ + this is valid +// '/test{1,}/', // Invalid quantifier -> this is valid + +declare(strict_types=1); + +namespace tests; + +use PHPUnit\Framework\TestCase; +use CoreLibs\Convert\Strings; + +/** + * Test class for CoreLibs\Convert\Strings regex validation methods + */ +class CoreLibsConvertStringsRegexValidateTest extends TestCase +{ + /** + * Test isValidRegex with valid regex patterns + */ + public function testIsValidRegexWithValidPatterns(): void + { + $validPatterns = [ + '/^[a-zA-Z0-9]+$/', + '/test/', + '/\d+/', + '/^hello.*world$/', + '/[0-9]{3}-[0-9]{3}-[0-9]{4}/', + '#^https?://.*#i', + '~^[a-z]+~', + '|test|', + '/^$/m', + '/\w+/u', + ]; + + foreach ($validPatterns as $pattern) { + $this->assertTrue( + Strings::isValidRegex($pattern), + "Pattern '{$pattern}' should be valid" + ); + } + } + + /** + * Test isValidRegex with invalid regex patterns + */ + public function testIsValidRegexWithInvalidPatterns(): void + { + $invalidPatterns = [ + '/[/', // Unmatched bracket + '/test[/', // Unmatched bracket + '/(?P/', // Unmatched parenthesis + '/(?P<>test)/', // Invalid named group + '/test\\/', // Invalid escape at end + '/(test/', // Unmatched parenthesis + '/test)/', // Unmatched parenthesis + // '/test{/', // Unmatched brace -> this is valid + // '/test{1,}/', // Invalid quantifier -> this is valid + '/[z-a]/', // Invalid character range + 'invalid', // No delimiters + '', // Empty string + '/(?P<123>test)/', // Invalid named group name + ]; + + foreach ($invalidPatterns as $pattern) { + $this->assertFalse( + Strings::isValidRegex($pattern), + "Pattern '{$pattern}' should be invalid" + ); + } + } + + /** + * Test getLastRegexErrorString returns correct error messages + */ + public function testGetLastRegexErrorStringReturnsCorrectMessages(): void + { + // Test with a valid regex first to ensure clean state + Strings::isValidRegex('/valid/'); + $this->assertEquals('No error', Strings::getLastRegexErrorString()); + + // Test with invalid regex to trigger an error + Strings::isValidRegex('/[/'); + $errorMessage = Strings::getLastRegexErrorString(); + + // The error message should be one of the defined messages + $this->assertContains($errorMessage, array_values(Strings::PREG_ERROR_MESSAGES)); + $this->assertNotEquals('Unknown error', $errorMessage); + } + + /** + * Test getLastRegexErrorString with unknown error + */ + public function testGetLastRegexErrorStringWithUnknownError(): void + { + // This is harder to test directly since we can't easily mock preg_last_error() + // but we can test the fallback behavior by reflection or assume it works + + // At minimum, ensure it returns a string + $result = Strings::getLastRegexErrorString(); + $this->assertIsString($result); + $this->assertNotEmpty($result); + } + + /** + * Test validateRegex with valid patterns + */ + public function testValidateRegexWithValidPatterns(): void + { + $validPatterns = [ + '/^test$/', + '/\d+/', + '/[a-z]+/i', + ]; + + foreach ($validPatterns as $pattern) { + $result = Strings::validateRegex($pattern); + + $this->assertIsArray($result); + $this->assertArrayHasKey('valid', $result); + $this->assertArrayHasKey('preg_error', $result); + $this->assertArrayHasKey('error', $result); + + $this->assertTrue($result['valid'], "Pattern '{$pattern}' should be valid"); + $this->assertEquals(PREG_NO_ERROR, $result['preg_error']); + $this->assertNull($result['error']); + } + } + + /** + * Test validateRegex with invalid patterns + */ + public function testValidateRegexWithInvalidPatterns(): void + { + $invalidPatterns = [ + '/[/', // Unmatched bracket + '/(?P/', // Unmatched parenthesis + '/test\\/', // Invalid escape at end + '/(test/', // Unmatched parenthesis + ]; + + foreach ($invalidPatterns as $pattern) { + $result = Strings::validateRegex($pattern); + + $this->assertIsArray($result); + $this->assertArrayHasKey('valid', $result); + $this->assertArrayHasKey('preg_error', $result); + $this->assertArrayHasKey('error', $result); + $this->assertArrayHasKey('pcre_error', $result); + + $this->assertFalse($result['valid'], "Pattern '{$pattern}' should be invalid"); + $this->assertNotEquals(PREG_NO_ERROR, $result['preg_error']); + $this->assertIsString($result['error']); + $this->assertNotNull($result['error']); + $this->assertNotEmpty($result['error']); + + // Verify error message is from our defined messages or 'Unknown error' + $this->assertTrue( + in_array($result['error'], array_values(Strings::PREG_ERROR_MESSAGES)) || + $result['error'] === 'Unknown error' + ); + } + } + + /** + * Test validateRegex array structure + */ + public function testValidateRegexArrayStructure(): void + { + $result = Strings::validateRegex('/test/'); + + // Test array structure for valid regex + $this->assertIsArray($result); + $this->assertCount(4, $result); + $this->assertArrayHasKey('valid', $result); + $this->assertArrayHasKey('preg_error', $result); + $this->assertArrayHasKey('error', $result); + + $result = Strings::validateRegex('/[/'); + + // Test array structure for invalid regex + $this->assertIsArray($result); + $this->assertCount(4, $result); + $this->assertArrayHasKey('valid', $result); + $this->assertArrayHasKey('preg_error', $result); + $this->assertArrayHasKey('error', $result); + $this->assertArrayHasKey('pcre_error', $result); + } + + /** + * Test that methods handle edge cases properly + */ + public function testEdgeCases(): void + { + // Empty string + $this->assertFalse(Strings::isValidRegex('')); + + $result = Strings::validateRegex(''); + $this->assertFalse($result['valid']); + + // Very long pattern + $longPattern = '/' . str_repeat('a', 1000) . '/'; + $this->assertTrue(Strings::isValidRegex($longPattern)); + + // Unicode patterns + $this->assertTrue(Strings::isValidRegex('/\p{L}+/u')); + $this->assertTrue(Strings::isValidRegex('/[α-ω]+/u')); + } + + /** + * Test PREG_ERROR_MESSAGES constant accessibility + */ + public function testPregErrorMessagesConstant(): void + { + $this->assertIsArray(Strings::PREG_ERROR_MESSAGES); + $this->assertNotEmpty(Strings::PREG_ERROR_MESSAGES); + + // Check that all expected PREG constants are defined + $expectedKeys = [ + PREG_NO_ERROR, + PREG_INTERNAL_ERROR, + PREG_BACKTRACK_LIMIT_ERROR, + PREG_RECURSION_LIMIT_ERROR, + PREG_BAD_UTF8_ERROR, + PREG_BAD_UTF8_OFFSET_ERROR, + PREG_JIT_STACKLIMIT_ERROR, + ]; + + foreach ($expectedKeys as $key) { + $this->assertArrayHasKey($key, Strings::PREG_ERROR_MESSAGES); + $this->assertIsString(Strings::PREG_ERROR_MESSAGES[$key]); + $this->assertNotEmpty(Strings::PREG_ERROR_MESSAGES[$key]); + } + } + + /** + * Test error state isolation between method calls + */ + public function testErrorStateIsolation(): void + { + // Start with invalid regex + Strings::isValidRegex('/[/'); + $firstError = Strings::getLastRegexErrorString(); + $this->assertNotEquals('No error', $firstError); + + // Use valid regex + Strings::isValidRegex('/valid/'); + $secondError = Strings::getLastRegexErrorString(); + $this->assertEquals('No error', $secondError); + + // Verify validateRegex clears previous errors + $result = Strings::validateRegex('/valid/'); + $this->assertTrue($result['valid']); + $this->assertEquals(PREG_NO_ERROR, $result['preg_error']); + } + + /** + * Test various regex delimiters + */ + public function testDifferentDelimiters(): void + { + $patterns = [ + '/test/', // forward slash + '#test#', // hash + '~test~', // tilde + '|test|', // pipe + '@test@', // at symbol + '!test!', // exclamation + '%test%', // percent + ]; + + foreach ($patterns as $pattern) { + $this->assertTrue( + Strings::isValidRegex($pattern), + "Pattern with delimiter '{$pattern}' should be valid" + ); + } + } +} + +// __END__ diff --git a/test/phpunit/Convert/CoreLibsConvertStringsTest.php b/test/phpunit/Convert/CoreLibsConvertStringsTest.php index c6c9225..ed4bbc9 100644 --- a/test/phpunit/Convert/CoreLibsConvertStringsTest.php +++ b/test/phpunit/Convert/CoreLibsConvertStringsTest.php @@ -24,117 +24,83 @@ final class CoreLibsConvertStringsTest extends TestCase { // 0: input // 1: format - // 2: split characters as string, null for default // 3: expected return [ 'all empty string' => [ '', '', - null, '' ], 'empty input string' => [ '', '2-2', - null, '' ], 'empty format string string' => [ '1234', '', - null, '1234' ], 'string format match' => [ '1234', '2-2', - null, '12-34' ], 'string format trailing match' => [ '1234', '2-2-', - null, '12-34' ], 'string format leading match' => [ '1234', '-2-2', - null, '12-34' ], 'string format double inside match' => [ '1234', '2--2', - null, '12--34', ], 'string format short first' => [ '1', '2-2', - null, '1' ], 'string format match first' => [ '12', '2-2', - null, '12' ], 'string format short second' => [ '123', '2-2', - null, '12-3' ], 'string format too long' => [ '1234567', '2-2', - null, '12-34-567' ], - 'string format invalid format string' => [ - '1234', - '2_2', - null, - '1234' - ], 'different split character' => [ '1234', '2_2', - '_', '12_34' ], 'mixed split characters' => [ '123456', '2-2_2', - '-_', '12-34_56' ], 'length mixed' => [ 'ABCD12345568ABC13', '2-4_5-2#4', - '-_#', 'AB-CD12_34556-8A#BC13' ], 'split with split chars in string' => [ '12-34', '2-2', - null, '12--3-4' ], - 'mutltibyte string' => [ - 'あいうえ', - '2-2', - null, - 'あいうえ' - ], - 'mutltibyte split string' => [ - '1234', - '2-2', - null, - '1234' - ], ]; } @@ -143,29 +109,137 @@ final class CoreLibsConvertStringsTest extends TestCase * * @covers ::splitFormatString * @dataProvider splitFormatStringProvider - * @testdox splitFormatString $input with format $format and splitters $split_characters will be $expected [$_dataName] + * @testdox splitFormatString $input with format $format will be $expected [$_dataName] * * @param string $input * @param string $format - * @param string|null $split_characters * @param string $expected * @return void */ public function testSplitFormatString( string $input, string $format, + string $expected + ): void { + $output = \CoreLibs\Convert\Strings::splitFormatString( + $input, + $format, + ); + $this->assertEquals( + $expected, + $output + ); + } + + /** check exceptions */ + public function splitFormatStringExceptionProvider(): array + { + return [ + 'string format with no splitter match' => [ + '1234', + '22', + '12-34' + ], + 'invalid format string' => [ + '1234', + '2あ2', + ], + 'mutltibyte string' => [ + 'あいうえ', + '2-2', + ], + 'mutltibyte split string' => [ + '1234', + '2-2', + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::splitFormatStringFixed + * @dataProvider splitFormatStringExceptionProvider + * @testdox splitFormatString Exception catch checks for $input with $format[$_dataName] + * + * @return void + */ + public function testSplitFormatStringExceptions(string $input, string $format): void + { + // catch exception + $this->expectException(\InvalidArgumentException::class); + \CoreLibs\Convert\Strings::splitFormatString($input, $format); + } + + /** + * test for split Format string fixed length + * + * @return array + */ + public function splitFormatStringFixedProvider(): array + { + return [ + 'normal split, default split char' => [ + 'abcdefg', + 4, + null, + 'abcd-efg' + ], + 'noraml split, other single split char' => [ + 'abcdefg', + 4, + "=", + 'abcd=efg' + ], + 'noraml split, other multiple split char' => [ + 'abcdefg', + 4, + "-=-", + 'abcd-=-efg' + ], + 'non ascii characters' => [ + 'あいうえお', + 2, + "-", + 'あい-うえ-お' + ], + 'empty string' => [ + '', + 4, + "-", + '' + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::splitFormatStringFixed + * @dataProvider splitFormatStringFixedProvider + * @testdox splitFormatStringFixed $input with length $split_length and split chars $split_characters will be $expected [$_dataName] + * + * @param string $input + * @param int $split_length + * @param string|null $split_characters + * @param string $expected + * @return void + */ + public function testSplitFormatStringFixed( + string $input, + int $split_length, ?string $split_characters, string $expected ): void { if ($split_characters === null) { - $output = \CoreLibs\Convert\Strings::splitFormatString( + $output = \CoreLibs\Convert\Strings::splitFormatStringFixed( $input, - $format + $split_length ); } else { - $output = \CoreLibs\Convert\Strings::splitFormatString( + $output = \CoreLibs\Convert\Strings::splitFormatStringFixed( $input, - $format, + $split_length, $split_characters ); } @@ -175,6 +249,36 @@ final class CoreLibsConvertStringsTest extends TestCase ); } + public function splitFormatStringFixedExceptionProvider(): array + { + return [ + 'split length too short' => [ + 'abcdefg', + -1, + ], + 'split length longer than string' => [ + 'abcdefg', + 20, + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::splitFormatStringFixed + * @dataProvider splitFormatStringFixedExceptionProvider + * @testdox splitFormatStringFixed Exception catch checks for $input with $length [$_dataName] + * + * @return void + */ + public function testSplitFormatStringFixedExceptions(string $input, int $length): void + { + // catch exception + $this->expectException(\InvalidArgumentException::class); + \CoreLibs\Convert\Strings::splitFormatStringFixed($input, $length); + } + /** * Undocumented function * @@ -378,6 +482,222 @@ final class CoreLibsConvertStringsTest extends TestCase \CoreLibs\Convert\Strings::stripUTF8BomBytes($file) ); } + + /** + * Undocumented function + * + * @return array + */ + public function allCharsInSetProvider(): array + { + return [ + 'find' => [ + 'abc', + 'abcdef', + true + ], + 'not found' => [ + 'abcz', + 'abcdef', + false + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::allCharsInSet + * @dataProvider allCharsInSetProvider + * @testdox allCharsInSet $input in $haystack with expected $expected [$_dataName] + * + * @param string $needle + * @param string $haystack + * @param bool $expected + * @return void + */ + public function testAllCharsInSet(string $needle, string $haystack, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Strings::allCharsInSet($needle, $haystack) + ); + } + + public function buildCharStringFromListsProvider(): array + { + return [ + 'test a' => [ + 'abc', + ['a', 'b', 'c'], + ], + 'test b' => [ + 'abc123', + ['a', 'b', 'c'], + ['1', '2', '3'], + ], + 'test c: no params' => [ + '', + ], + 'test c: empty 1' => [ + '', + [] + ], + 'test nested' => [ + 'abc', + [['a'], ['b'], ['c']], + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::buildCharStringFromLists + * @dataProvider buildCharStringFromListsProvider + * @testdox buildCharStringFromLists all $input convert to $expected [$_dataName] + * + * @param string $expected + * @param array ...$input + * @return void + */ + public function testBuildCharStringFromLists(string $expected, array ...$input): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Strings::buildCharStringFromLists(...$input) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function removeDuplicatesProvider(): array + { + return [ + 'test no change' => [ + 'ABCDEFG', + 'ABCDEFG', + ], + 'test simple' => [ + 'aa', + 'a' + ], + 'test keep lower and uppwer case' => [ + 'AaBbCc', + 'AaBbCc' + ], + 'test unqiue' => [ + 'aabbcc', + 'abc' + ], + 'test multibyte no change' => [ + 'あいうえお', + 'あいうえお', + ], + 'test multibyte' => [ + 'ああいいううええおお', + 'あいうえお', + ], + 'test multibyte special' => [ + 'あぁいぃうぅえぇおぉ', + 'あぁいぃうぅえぇおぉ', + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::removeDuplicates + * @dataProvider removeDuplicatesProvider + * @testdox removeDuplicates make $input unqiue to $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testRemoveDuplicates(string $input, string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Strings::removeDuplicates($input) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function isValidRegexSimpleProvider(): array + { + return [ + 'valid regex' => [ + '/^[A-z]$/', + true, + [ + 'valid' => true, + 'preg_error' => 0, + 'error' => null, + 'pcre_error' => null + ], + ], + 'invalid regex A' => [ + '/^[A-z]$', + false, + [ + 'valid' => false, + 'preg_error' => 1, + 'error' => 'Internal PCRE error', + 'pcre_error' => 'Internal error' + ], + ], + 'invalid regex B' => [ + '/^[A-z$', + false, + [ + 'valid' => false, + 'preg_error' => 1, + 'error' => 'Internal PCRE error', + 'pcre_error' => 'Internal error' + ], + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::isValidRegexSimple + * @dataProvider isValidRegexSimpleProvider + * @testdox isValidRegexSimple make $input unqiue to $expected [$_dataName] + * + * @param string $input + * @param bool $expected + * @return void + */ + public function testIsValidRegexSimple(string $input, bool $expected, array $expected_extended): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Strings::isValidRegex($input), + 'Regex is not valid' + ); + $this->assertEquals( + $expected_extended, + \CoreLibs\Convert\Strings::validateRegex($input), + 'Validation of regex failed' + ); + $this->assertEquals( + // for true null is set, so we get here No Error + $expected_extended['error'] ?? \CoreLibs\Convert\Strings::PREG_ERROR_MESSAGES[0], + \CoreLibs\Convert\Strings::getLastRegexErrorString(), + 'Cannot match last preg error string' + ); + } } // __END__ diff --git a/test/phpunit/Create/CoreLibsCreateRandomKeyTest.php b/test/phpunit/Create/CoreLibsCreateRandomKeyTest.php index 3662aa6..353ec25 100644 --- a/test/phpunit/Create/CoreLibsCreateRandomKeyTest.php +++ b/test/phpunit/Create/CoreLibsCreateRandomKeyTest.php @@ -13,32 +13,6 @@ use PHPUnit\Framework\TestCase; */ final class CoreLibsCreateRandomKeyTest extends TestCase { - /** - * Undocumented function - * - * @return array - */ - public function keyLenghtProvider(): array - { - return [ - 'valid key length' => [ - 0 => 6, - 1 => true, - 2 => 6, - ], - 'negative key length' => [ - 0 => -1, - 1 => false, - 2 => 4, - ], - 'tpp big key length' => [ - 0 => 300, - 1 => false, - 2 => 4, - ], - ]; - } - /** * Undocumented function * @@ -47,109 +21,67 @@ final class CoreLibsCreateRandomKeyTest extends TestCase public function randomKeyGenProvider(): array { return [ - 'default key length' => [ + // just key length + 'default key length, default char set' => [ 0 => null, - 1 => 4 + 1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT ], - 'set -1 key length default' => [ + 'set -1 key length, default char set' => [ 0 => -1, - 1 => 4, + 1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT, ], - 'set too large key length' => [ + 'set 0 key length, default char set' => [ + 0 => -1, + 1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT, + ], + 'set too large key length, default char set' => [ 0 => 300, - 1 => 4, + 1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT, ], - 'set override key lenght' => [ + 'set override key lenght, default char set' => [ 0 => 6, 1 => 6, ], + // just character set + 'default key length, different char set A' => [ + 0 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT, + 1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT, + 2 => [ + 'A', 'B', 'C' + ], + ], + 'different key length, different char set B' => [ + 0 => 16, + 1 => 16, + 2 => [ + 'A', 'B', 'C' + ], + 3 => [ + '1', '2', '3' + ] + ], ]; } + // Alternative more efficient version using strpos /** - * 1 + * check if all characters are in set * - * @return array + * @param string $input + * @param string $allowed_chars + * @return bool */ - public function keepKeyLengthProvider(): array + private function allCharsInSet(string $input, string $allowed_chars): bool { - return [ - 'set too large' => [ - 0 => 6, - 1 => 300, - 2 => 6, - ], - 'set too small' => [ - 0 => 8, - 1 => -2, - 2 => 8, - ], - 'change valid' => [ - 0 => 10, - 1 => 6, - 2 => 6, - ] - ]; - } + $inputLength = strlen($input); - /** - * run before each test and reset to default 4 - * - * @before - * - * @return void - */ - public function resetKeyLength(): void - { - \CoreLibs\Create\RandomKey::setRandomKeyLength(4); - } - - /** - * check that first length is 4 - * - * @covers ::getRandomKeyLength - * @testWith [4] - * @testdox getRandomKeyLength on init will be $expected [$_dataName] - * - * @param integer $expected - * @return void - */ - public function testGetRandomKeyLengthInit(int $expected): void - { - $this->assertEquals( - $expected, - \CoreLibs\Create\RandomKey::getRandomKeyLength() - ); - } - - /** - * Undocumented function - * - * @covers ::setRandomKeyLength - * @covers ::getRandomKeyLength - * @dataProvider keyLenghtProvider - * @testdox setRandomKeyLength $input will be $expected, compare to $compare [$_dataName] - * - * @param integer $input - * @param boolean $expected - * @param integer $compare - * @return void - */ - public function testSetRandomKeyLength(int $input, bool $expected, int $compare): void - { - // set - $this->assertEquals( - $expected, - \CoreLibs\Create\RandomKey::setRandomKeyLength($input) - ); - // read test, if false, use compare check - if ($expected === false) { - $input = $compare; + for ($i = 0; $i < $inputLength; $i++) { + if (strpos($allowed_chars, $input[$i]) === false) { + return false; + } } - $this->assertEquals( - $input, - \CoreLibs\Create\RandomKey::getRandomKeyLength() - ); + + return true; } /** @@ -163,43 +95,41 @@ final class CoreLibsCreateRandomKeyTest extends TestCase * @param integer $expected * @return void */ - public function testRandomKeyGen(?int $input, int $expected): void + public function testRandomKeyGen(?int $input, int $expected, array ...$key_range): void { + $__key_data = \CoreLibs\Create\RandomKey::KEY_CHARACTER_RANGE_DEFAULT; + if (count($key_range)) { + $__key_data = join('', array_unique(array_merge(...$key_range))); + } if ($input === null) { $this->assertEquals( $expected, strlen(\CoreLibs\Create\RandomKey::randomKeyGen()) ); - } else { + } elseif ($input !== null && !count($key_range)) { + $random_key = \CoreLibs\Create\RandomKey::randomKeyGen($input); + $this->assertTrue( + $this->allCharsInSet($random_key, $__key_data), + 'Characters not valid' + ); $this->assertEquals( $expected, - strlen(\CoreLibs\Create\RandomKey::randomKeyGen($input)) + strlen($random_key), + 'String length not matching' + ); + } elseif (count($key_range)) { + $random_key = \CoreLibs\Create\RandomKey::randomKeyGen($input, ...$key_range); + $this->assertTrue( + $this->allCharsInSet($random_key, $__key_data), + 'Characters not valid' + ); + $this->assertEquals( + $expected, + strlen($random_key), + 'String length not matching' ); } } - - /** - * Check that if set to n and then invalid, it keeps the previous one - * or if second change valid, second will be shown - * - * @covers ::setRandomKeyLength - * @dataProvider keepKeyLengthProvider - * @testdox keep setRandomKeyLength set with $input_valid and then $input_invalid will be $expected [$_dataName] - * - * @param integer $input_valid - * @param integer $input_invalid - * @param integer $expected - * @return void - */ - public function testKeepKeyLength(int $input_valid, int $input_invalid, int $expected): void - { - \CoreLibs\Create\RandomKey::setRandomKeyLength($input_valid); - \CoreLibs\Create\RandomKey::setRandomKeyLength($input_invalid); - $this->assertEquals( - $expected, - \CoreLibs\Create\RandomKey::getRandomKeyLength() - ); - } } // __END__ diff --git a/test/phpunit/Logging/CoreLibsLoggingLoggingTest.php b/test/phpunit/Logging/CoreLibsLoggingLoggingTest.php index 3d2b389..13b888f 100644 --- a/test/phpunit/Logging/CoreLibsLoggingLoggingTest.php +++ b/test/phpunit/Logging/CoreLibsLoggingLoggingTest.php @@ -249,7 +249,7 @@ final class CoreLibsLoggingLoggingTest extends TestCase $this->assertFalse( $log->loggingLevelIsDebug() ); - // not set, should be debug] + // not set, should be debug $log = new \CoreLibs\Logging\Logging([ 'log_file_id' => 'testSetLoggingLevel', 'log_folder' => self::LOG_FOLDER, @@ -297,6 +297,71 @@ final class CoreLibsLoggingLoggingTest extends TestCase $log->setLoggingLevel('NotGood'); } + /** + * Undocumented function + * + * @covers ::setErrorLogWriteLevel + * @covers ::getErrorLogWriteLevel + * @testdox setErrorLogWriteLevel set/get checks + * + * @return void + */ + public function testSetErrorLogWriteLevel(): void + { + // valid that is not Debug + $log = new \CoreLibs\Logging\Logging([ + 'log_file_id' => 'testSetErrorLogWriteLevel', + 'log_folder' => self::LOG_FOLDER, + 'error_log_write_level' => Level::Error + ]); + $this->assertEquals( + Level::Error, + $log->getErrorLogWriteLevel() + ); + // not set on init + $log = new \CoreLibs\Logging\Logging([ + 'log_file_id' => 'testSetErrorLogWriteLevel', + 'log_folder' => self::LOG_FOLDER, + ]); + $this->assertEquals( + Level::Emergency, + $log->getErrorLogWriteLevel() + ); + // invalid, should be Emergency, will throw excpetion too + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage( + 'Option: "error_log_write_level" is not of instance \CoreLibs\Logging\Logger\Level' + ); + $log = new \CoreLibs\Logging\Logging([ + 'log_file_id' => 'testSetLoggingLevel', + 'log_folder' => self::LOG_FOLDER, + 'error_log_write_level' => 'I' + ]); + $this->assertEquals( + Level::Emergency, + $log->getErrorLogWriteLevel() + ); + // set valid then change + $log = new \CoreLibs\Logging\Logging([ + 'log_file_id' => 'testSetErrorLogWriteLevel', + 'log_folder' => self::LOG_FOLDER, + 'error_log_write_level' => Level::Error + ]); + $this->assertEquals( + Level::Error, + $log->getErrorLogWriteLevel() + ); + $log->setErrorLogWriteLevel(Level::Notice); + $this->assertEquals( + Level::Notice, + $log->getErrorLogWriteLevel() + ); + // illegal logging level + $this->expectException(\Psr\Log\InvalidArgumentException::class); + $this->expectExceptionMessageMatches("/^Level \"NotGood\" is not defined, use one of: /"); + $log->setErrorLogWriteLevel('NotGood'); + } + // setLogFileId // getLogFileId