X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=CRM%2FUtils%2FType.php;h=9eb9702f016abcb03a221cfc8d02be3334d303bd;hb=b0aca9b6e08b58fc4c69977a124b5af866d6990b;hp=01d833e72b829a9a2035e10cd81edab9588c1e33;hpb=1c5170d730859349ae09942fdf1a53757391ab84;p=civicrm-core.git diff --git a/CRM/Utils/Type.php b/CRM/Utils/Type.php index 01d833e72b..9eb9702f01 100644 --- a/CRM/Utils/Type.php +++ b/CRM/Utils/Type.php @@ -334,11 +334,10 @@ class CRM_Utils_Type { default: throw new CRM_Core_Exception( - $type . " is not a recognised (camel cased) data type." + $type . " is not a recognized (camel cased) data type." ); } - // @todo Use exceptions instead of CRM_Core_Error::fatal(). if ($abort) { $data = htmlentities($data); @@ -473,6 +472,101 @@ class CRM_Utils_Type { return NULL; } + /** + * Validate that a value matches a PHP type. + * + * Note that, at a micro-level, this is probably slower than using real PHP type-checking, but it doesn't seem bad. + * (In light benchmarking of ~1000 validations on an i3-10100, there is no obvious effect on the execution-time.) + * Should be fast enough for validating business entities. + * + * Example usage: 'validatePhpType(123, 'int|double');` + * + * @param mixed $value + * @param string|string[] $types + * The list of acceptable PHP types and/or classnames. + * Either an array or a string (with '|' delimiters). + * Note that 'null' is a distinct type. + * Ex: 'int' + * Ex: 'Countable|null' + * Ex: 'string|bool' + * Ex: 'string|false' + * @param bool $isStrict + * If data is likely to come from another text medium, then you may want to + * allow (say) numbers and string-like-numbers to be used interchangably. + * + * With $isStrict=TRUE, the string "123" does not match type "int". The int 456 does not match type "double". etc. + * + * With $isStrict=FALSE, the string "123" will match types "string", "int", and "double". + * @return bool + */ + public static function validatePhpType($value, $types, bool $isStrict = TRUE) { + if (is_string($types)) { + $types = preg_split('/ *\| */', $types); + } + + $checkTypeStrict = function($type, $value) { + static $aliases = ['integer' => 'int', 'boolean' => 'bool', 'float' => 'double', 'NULL' => 'null']; + switch ($type) { + case 'mixed': + return TRUE; + + case 'false': + case 'FALSE': + case 'true': + case 'TRUE': + $expectBool = mb_strtolower($type) === 'true'; + return $value === $expectBool; + } + $realType = gettype($value); + if (($aliases[$realType] ?? $realType) === ($aliases[$type] ?? $type)) { + return TRUE; + } + if ($realType === 'object' && $value instanceof $type) { + return TRUE; + } + return FALSE; + }; + $checkTypeRelaxed = function($type, $value) use ($checkTypeStrict) { + switch ($type) { + case 'string': + return is_string($value) || is_int($value) || is_float($value); + + case 'bool': + case 'boolean': + return is_bool($value) || CRM_Utils_Rule::integer($value); + + case 'int': + case' integer': + return CRM_Utils_Rule::integer($value); + + case 'float': + case 'double': + return CRM_Utils_Rule::numeric($value); + + default: + return $checkTypeStrict($type, $value); + } + }; + $checkType = $isStrict ? $checkTypeStrict : $checkTypeRelaxed; + + foreach ($types as $type) { + $isTypedArray = substr($type, -2, 2) === '[]'; + if (!$isTypedArray && $checkType($type, $value)) { + return TRUE; + } + if ($isTypedArray && is_array($value)) { + $baseType = substr($type, 0, -2); + foreach ($value as $vItem) { + if (!\CRM_Utils_Type::validatePhpType($vItem, [$baseType], $isStrict)) { + continue 2; + } + } + return TRUE; + } + } + return FALSE; + } + /** * Preg_replace_callback for mysqlOrderByFieldFunction escape. * @@ -512,7 +606,7 @@ class CRM_Utils_Type { } /** - * Get list of avaliable Data Types for Option Groups + * Get list of available Data Types for Option Groups * * @return array */