diff --git a/src/Libs/Validator.php b/src/Libs/Validator.php index 7551b3d..8b4afd8 100644 --- a/src/Libs/Validator.php +++ b/src/Libs/Validator.php @@ -66,6 +66,30 @@ class Validator return true; } + /** + * Splits a single rule string into its name and its raw argument string. + * + * The rule name is everything before the first ':'. The remainder is + * returned verbatim (it is NOT split further here) so that rules whose + * arguments legitimately contain ':' or ',' - such as regex or mimes - + * are preserved intact. The caller decides how to split the arguments. + * + * @param string $rule The rule to parse. Ex: "regex:/^a,b$/" or "enum:a,b". + * + * @return array A two element array: [name, rawArguments]. When the rule + * has no parameters, rawArguments is an empty string. + */ + public static function parseRule(string $rule): array + { + $separator = strpos($rule, ':'); + + if ($separator === false) { + return [$rule, '']; + } + + return [substr($rule, 0, $separator), substr($rule, $separator + 1)]; + } + /** * Checks if a rule is met. * @@ -77,15 +101,46 @@ class Validator */ public static function checkRule(mixed $subject, string $rule): bool { - $arguments = preg_split('/[:,]/', $rule); - $rule = [static::class, $arguments[0]]; - $arguments[0] = $subject; + [$name, $rawArguments] = static::parseRule($rule); - if (is_callable($rule)) { - return call_user_func_array($rule, $arguments); + $method = [static::class, $name]; + $arguments = array_merge([$subject], static::splitArguments($name, $rawArguments)); + + if (is_callable($method)) { + return call_user_func_array($method, $arguments); } - throw new Exception('Bad rule: "' . preg_split('/::/', $rule)[1] . '"'); + throw new Exception('Bad rule: "' . $name . '"'); + } + + /** + * Splits a rule's raw argument string into the arguments to pass to the + * rule's method (the subject is not included). + * + * Most rules take a comma-separated list of values (e.g. "enum:a,b"). + * A few rules receive an argument that must be kept intact because it can + * legitimately contain commas or colons: "regex" (a pattern) and "not" + * (a sub-rule that is itself parsed recursively). An empty argument string + * yields no extra arguments. + * + * @param string $name The rule name. + * @param string $raw The raw argument string as returned by parseRule(). + * + * @return array The list of arguments to pass after the subject. + */ + private static function splitArguments(string $name, string $raw): array + { + if ($raw === '') { + return []; + } + + $intactRules = ['regex', 'not']; + + if (in_array($name, $intactRules, true)) { + return [$raw]; + } + + return explode(',', $raw); } /**