Validators.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (https://nette.org)
  4. * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  5. */
  6. namespace Nette\Utils;
  7. use Nette;
  8. /**
  9. * Validation utilities.
  10. */
  11. class Validators
  12. {
  13. use Nette\StaticClass;
  14. protected static $validators = [
  15. 'bool' => 'is_bool',
  16. 'boolean' => 'is_bool',
  17. 'int' => 'is_int',
  18. 'integer' => 'is_int',
  19. 'float' => 'is_float',
  20. 'number' => [__CLASS__, 'isNumber'],
  21. 'numeric' => [__CLASS__, 'isNumeric'],
  22. 'numericint' => [__CLASS__, 'isNumericInt'],
  23. 'string' => 'is_string',
  24. 'unicode' => [__CLASS__, 'isUnicode'],
  25. 'array' => 'is_array',
  26. 'list' => [Arrays::class, 'isList'],
  27. 'object' => 'is_object',
  28. 'resource' => 'is_resource',
  29. 'scalar' => 'is_scalar',
  30. 'callable' => [__CLASS__, 'isCallable'],
  31. 'null' => 'is_null',
  32. 'email' => [__CLASS__, 'isEmail'],
  33. 'url' => [__CLASS__, 'isUrl'],
  34. 'uri' => [__CLASS__, 'isUri'],
  35. 'none' => [__CLASS__, 'isNone'],
  36. 'type' => [__CLASS__, 'isType'],
  37. 'identifier' => [__CLASS__, 'isPhpIdentifier'],
  38. 'pattern' => null,
  39. 'alnum' => 'ctype_alnum',
  40. 'alpha' => 'ctype_alpha',
  41. 'digit' => 'ctype_digit',
  42. 'lower' => 'ctype_lower',
  43. 'upper' => 'ctype_upper',
  44. 'space' => 'ctype_space',
  45. 'xdigit' => 'ctype_xdigit',
  46. 'iterable' => [__CLASS__, 'isIterable'],
  47. ];
  48. protected static $counters = [
  49. 'string' => 'strlen',
  50. 'unicode' => [Strings::class, 'length'],
  51. 'array' => 'count',
  52. 'list' => 'count',
  53. 'alnum' => 'strlen',
  54. 'alpha' => 'strlen',
  55. 'digit' => 'strlen',
  56. 'lower' => 'strlen',
  57. 'space' => 'strlen',
  58. 'upper' => 'strlen',
  59. 'xdigit' => 'strlen',
  60. ];
  61. /**
  62. * Throws exception if a variable is of unexpected type.
  63. * @param mixed
  64. * @param string expected types separated by pipe
  65. * @param string label
  66. * @return void
  67. */
  68. public static function assert($value, $expected, $label = 'variable')
  69. {
  70. if (!static::is($value, $expected)) {
  71. $expected = str_replace(['|', ':'], [' or ', ' in range '], $expected);
  72. if (is_array($value)) {
  73. $type = 'array(' . count($value) . ')';
  74. } elseif (is_object($value)) {
  75. $type = 'object ' . get_class($value);
  76. } elseif (is_string($value) && strlen($value) < 40) {
  77. $type = "string '$value'";
  78. } else {
  79. $type = gettype($value);
  80. }
  81. throw new AssertionException("The $label expects to be $expected, $type given.");
  82. }
  83. }
  84. /**
  85. * Throws exception if an array field is missing or of unexpected type.
  86. * @param array
  87. * @param string item
  88. * @param string expected types separated by pipe
  89. * @param string
  90. * @return void
  91. */
  92. public static function assertField($arr, $field, $expected = null, $label = "item '%' in array")
  93. {
  94. self::assert($arr, 'array', 'first argument');
  95. if (!array_key_exists($field, $arr)) {
  96. throw new AssertionException('Missing ' . str_replace('%', $field, $label) . '.');
  97. } elseif ($expected) {
  98. static::assert($arr[$field], $expected, str_replace('%', $field, $label));
  99. }
  100. }
  101. /**
  102. * Finds whether a variable is of expected type.
  103. * @param mixed
  104. * @param string expected types separated by pipe with optional ranges
  105. * @return bool
  106. */
  107. public static function is($value, $expected)
  108. {
  109. foreach (explode('|', $expected) as $item) {
  110. if (substr($item, -2) === '[]') {
  111. if (self::everyIs($value, substr($item, 0, -2))) {
  112. return true;
  113. }
  114. continue;
  115. }
  116. list($type) = $item = explode(':', $item, 2);
  117. if (isset(static::$validators[$type])) {
  118. if (!call_user_func(static::$validators[$type], $value)) {
  119. continue;
  120. }
  121. } elseif ($type === 'pattern') {
  122. if (preg_match('|^' . (isset($item[1]) ? $item[1] : '') . '\z|', $value)) {
  123. return true;
  124. }
  125. continue;
  126. } elseif (!$value instanceof $type) {
  127. continue;
  128. }
  129. if (isset($item[1])) {
  130. $length = $value;
  131. if (isset(static::$counters[$type])) {
  132. $length = call_user_func(static::$counters[$type], $value);
  133. }
  134. $range = explode('..', $item[1]);
  135. if (!isset($range[1])) {
  136. $range[1] = $range[0];
  137. }
  138. if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) {
  139. continue;
  140. }
  141. }
  142. return true;
  143. }
  144. return false;
  145. }
  146. /**
  147. * Finds whether all values are of expected type.
  148. * @param array|\Traversable
  149. * @param string expected types separated by pipe with optional ranges
  150. * @return bool
  151. */
  152. public static function everyIs($values, $expected)
  153. {
  154. if (!self::isIterable($values)) {
  155. return false;
  156. }
  157. foreach ($values as $value) {
  158. if (!static::is($value, $expected)) {
  159. return false;
  160. }
  161. }
  162. return true;
  163. }
  164. /**
  165. * Finds whether a value is an integer or a float.
  166. * @return bool
  167. */
  168. public static function isNumber($value)
  169. {
  170. return is_int($value) || is_float($value);
  171. }
  172. /**
  173. * Finds whether a value is an integer.
  174. * @return bool
  175. */
  176. public static function isNumericInt($value)
  177. {
  178. return is_int($value) || is_string($value) && preg_match('#^-?[0-9]+\z#', $value);
  179. }
  180. /**
  181. * Finds whether a string is a floating point number in decimal base.
  182. * @return bool
  183. */
  184. public static function isNumeric($value)
  185. {
  186. return is_float($value) || is_int($value) || is_string($value) && preg_match('#^-?[0-9]*[.]?[0-9]+\z#', $value);
  187. }
  188. /**
  189. * Finds whether a value is a syntactically correct callback.
  190. * @return bool
  191. */
  192. public static function isCallable($value)
  193. {
  194. return $value && is_callable($value, true);
  195. }
  196. /**
  197. * Finds whether a value is an UTF-8 encoded string.
  198. * @param string
  199. * @return bool
  200. */
  201. public static function isUnicode($value)
  202. {
  203. return is_string($value) && preg_match('##u', $value);
  204. }
  205. /**
  206. * Finds whether a value is "falsy".
  207. * @return bool
  208. */
  209. public static function isNone($value)
  210. {
  211. return $value == null; // intentionally ==
  212. }
  213. /**
  214. * Finds whether a variable is a zero-based integer indexed array.
  215. * @param array
  216. * @return bool
  217. */
  218. public static function isList($value)
  219. {
  220. return Arrays::isList($value);
  221. }
  222. /**
  223. * Is a value in specified range?
  224. * @param mixed
  225. * @param array min and max value pair
  226. * @return bool
  227. */
  228. public static function isInRange($value, $range)
  229. {
  230. if ($value === null || !(isset($range[0]) || isset($range[1]))) {
  231. return false;
  232. }
  233. $limit = isset($range[0]) ? $range[0] : $range[1];
  234. if (is_string($limit)) {
  235. $value = (string) $value;
  236. } elseif ($limit instanceof \DateTimeInterface) {
  237. if (!$value instanceof \DateTimeInterface) {
  238. return false;
  239. }
  240. } elseif (is_numeric($value)) {
  241. $value *= 1;
  242. } else {
  243. return false;
  244. }
  245. return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1]));
  246. }
  247. /**
  248. * Finds whether a string is a valid email address.
  249. * @param string
  250. * @return bool
  251. */
  252. public static function isEmail($value)
  253. {
  254. $atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part
  255. $alpha = "a-z\x80-\xFF"; // superset of IDN
  256. return (bool) preg_match("(^
  257. (\"([ !#-[\\]-~]*|\\\\[ -~])+\"|$atom+(\\.$atom+)*) # quoted or unquoted
  258. @
  259. ([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+ # domain - RFC 1034
  260. [$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
  261. \\z)ix", $value);
  262. }
  263. /**
  264. * Finds whether a string is a valid http(s) URL.
  265. * @param string
  266. * @return bool
  267. */
  268. public static function isUrl($value)
  269. {
  270. $alpha = "a-z\x80-\xFF";
  271. return (bool) preg_match("(^
  272. https?://(
  273. (([-_0-9$alpha]+\\.)* # subdomain
  274. [0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)? # domain
  275. [$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
  276. |\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} # IPv4
  277. |\[[0-9a-f:]{3,39}\] # IPv6
  278. )(:\\d{1,5})? # port
  279. (/\\S*)? # path
  280. \\z)ix", $value);
  281. }
  282. /**
  283. * Finds whether a string is a valid URI according to RFC 1738.
  284. * @param string
  285. * @return bool
  286. */
  287. public static function isUri($value)
  288. {
  289. return (bool) preg_match('#^[a-z\d+\.-]+:\S+\z#i', $value);
  290. }
  291. /**
  292. * Checks whether the input is a class, interface or trait.
  293. * @param string
  294. * @return bool
  295. */
  296. public static function isType($type)
  297. {
  298. return class_exists($type) || interface_exists($type) || trait_exists($type);
  299. }
  300. /**
  301. * Checks whether the input is a valid PHP identifier.
  302. * @return bool
  303. */
  304. public static function isPhpIdentifier($value)
  305. {
  306. return is_string($value) && preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\z#', $value);
  307. }
  308. /**
  309. * Returns true if value is iterable (array or instance of Traversable).
  310. * @return bool
  311. */
  312. private static function isIterable($value)
  313. {
  314. return is_array($value) || $value instanceof \Traversable;
  315. }
  316. }