validator.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. <?php namespace System;
  2. class Validator {
  3. /**
  4. * The array being validated.
  5. *
  6. * @var array
  7. */
  8. public $attributes;
  9. /**
  10. * The validation rules.
  11. *
  12. * @var array
  13. */
  14. public $rules;
  15. /**
  16. * The validation messages.
  17. *
  18. * @var array
  19. */
  20. public $messages;
  21. /**
  22. * The post-validation error messages.
  23. *
  24. * @var array
  25. */
  26. public $errors;
  27. /**
  28. * The "size" related validation rules.
  29. *
  30. * @var array
  31. */
  32. protected $size_rules = array('size', 'between', 'min', 'max');
  33. /**
  34. * Create a new validator instance.
  35. *
  36. * @param array $attributes
  37. * @param array $rules
  38. * @param array $messages
  39. * @return void
  40. */
  41. public function __construct($attributes, $rules, $messages = array())
  42. {
  43. $this->attributes = $attributes;
  44. $this->rules = $rules;
  45. $this->messages = $messages;
  46. }
  47. /**
  48. * Validate the target array using the specified validation rules.
  49. *
  50. * @return bool
  51. */
  52. public function invalid()
  53. {
  54. return ! $this->valid();
  55. }
  56. /**
  57. * Validate the target array using the specified validation rules.
  58. *
  59. * @return bool
  60. */
  61. public function valid()
  62. {
  63. $this->errors = new Validation\Errors;
  64. foreach ($this->rules as $attribute => $rules)
  65. {
  66. if (is_string($rules))
  67. {
  68. $rules = explode('|', $rules);
  69. }
  70. foreach ($rules as $rule)
  71. {
  72. $this->check($attribute, $rule);
  73. }
  74. }
  75. return count($this->errors->messages) == 0;
  76. }
  77. /**
  78. * Evaluate an attribute against a validation rule.
  79. *
  80. * @param string $attribute
  81. * @param string $rule
  82. * @return void
  83. */
  84. protected function check($attribute, $rule)
  85. {
  86. list($rule, $parameters) = $this->parse($rule);
  87. if ( ! method_exists($this, $validator = 'validate_'.$rule))
  88. {
  89. throw new \Exception("Validation rule [$rule] doesn't exist.");
  90. }
  91. // No validation will be run for attributes that do not exist unless the rule being validated
  92. // is "required" or "accepted". No other rules have implicit "required" checks.
  93. if ( ! array_key_exists($attribute, $this->attributes) and ! in_array($rule, array('required', 'accepted')))
  94. {
  95. continue;
  96. }
  97. if ( ! $this->$validator($attribute, $parameters))
  98. {
  99. $this->errors->add($attribute, $this->format_message($this->get_message($attribute, $rule), $attribute, $rule, $parameters));
  100. }
  101. }
  102. /**
  103. * Validate that a required attribute exists in the attributes array.
  104. *
  105. * @param string $attribute
  106. * @return bool
  107. */
  108. protected function validate_required($attribute)
  109. {
  110. return array_key_exists($attribute, $this->attributes) and trim($this->attributes[$attribute]) !== '';
  111. }
  112. /**
  113. * Validate that an attribute has a matching confirmation attribute.
  114. *
  115. * @param string $attribute
  116. * @return bool
  117. */
  118. protected function validate_confirmed($attribute)
  119. {
  120. return array_key_exists($attribute.'_confirmation', $this->attributes) and $this->attributes[$attribute] == $this->attributes[$attribute.'_confirmation'];
  121. }
  122. /**
  123. * Validate that an attribute was "accepted".
  124. *
  125. * This validation rule implies the attribute is "required".
  126. *
  127. * @param string $attribute
  128. * @return bool
  129. */
  130. protected function validate_accepted($attribute)
  131. {
  132. return static::validate_required($attribute) and ($this->attributes[$attribute] == 'yes' or $this->attributes[$attribute] == '1');
  133. }
  134. /**
  135. * Validate that an attribute is numeric.
  136. *
  137. * @param string $attribute
  138. * @return bool
  139. */
  140. protected function validate_numeric($attribute)
  141. {
  142. return is_numeric($this->attributes[$attribute]);
  143. }
  144. /**
  145. * Validate that an attribute is an integer.
  146. *
  147. * @param string $attribute
  148. * @return bool
  149. */
  150. protected function validate_integer($attribute)
  151. {
  152. return filter_var($this->attributes[$attribute], FILTER_VALIDATE_INT) !== false;
  153. }
  154. /**
  155. * Validate the size of an attribute.
  156. *
  157. * @param string $attribute
  158. * @param array $parameters
  159. * @return bool
  160. */
  161. protected function validate_size($attribute, $parameters)
  162. {
  163. return $this->get_size($attribute) == $parameters[0];
  164. }
  165. /**
  166. * Validate the size of an attribute is between a set of values.
  167. *
  168. * @param string $attribute
  169. * @param array $parameters
  170. * @return bool
  171. */
  172. protected function validate_between($attribute, $parameters)
  173. {
  174. return $this->get_size($attribute) >= $parameters[0] and $this->get_size($attribute) <= $parameters[1];
  175. }
  176. /**
  177. * Validate the size of an attribute is greater than a minimum value.
  178. *
  179. * @param string $attribute
  180. * @param array $parameters
  181. * @return bool
  182. */
  183. protected function validate_min($attribute, $parameters)
  184. {
  185. return $this->get_size($attribute) >= $parameters[0];
  186. }
  187. /**
  188. * Validate the size of an attribute is less than a maximum value.
  189. *
  190. * @param string $attribute
  191. * @param array $parameters
  192. * @return bool
  193. */
  194. protected function validate_max($attribute, $parameters)
  195. {
  196. return $this->get_size($attribute) <= $parameters[0];
  197. }
  198. /**
  199. * Get the size of an attribute.
  200. *
  201. * @param string $attribute
  202. * @return mixed
  203. */
  204. protected function get_size($attribute)
  205. {
  206. if ($this->has_numeric_rule($attribute))
  207. {
  208. return $this->attributes[$attribute];
  209. }
  210. return (array_key_exists($attribute, $_FILES)) ? $this->attributes[$attribute]['size'] / 1000 : Str::length(trim($this->attributes[$attribute]));
  211. }
  212. /**
  213. * Validate an attribute is contained within a list of values.
  214. *
  215. * @param string $attribute
  216. * @param array $parameters
  217. * @return bool
  218. */
  219. protected function validate_in($attribute, $parameters)
  220. {
  221. return in_array($this->attributes[$attribute], $parameters);
  222. }
  223. /**
  224. * Validate an attribute is not contained within a list of values.
  225. *
  226. * @param string $attribute
  227. * @param array $parameters
  228. * @return bool
  229. */
  230. protected function validate_not_in($attribute, $parameters)
  231. {
  232. return ! in_array($this->attributes[$attribute], $parameters);
  233. }
  234. /**
  235. * Validate the uniqueness of an attribute value on a given database table.
  236. *
  237. * @param string $attribute
  238. * @param array $parameters
  239. * @return bool
  240. */
  241. protected function validate_unique($attribute, $parameters)
  242. {
  243. if ( ! isset($parameters[1]))
  244. {
  245. $parameters[1] = $attribute;
  246. }
  247. return DB::table($parameters[0])->where($parameters[1], '=', $this->attributes[$attribute])->count() == 0;
  248. }
  249. /**
  250. * Validate than an attribute is a valid e-mail address.
  251. *
  252. * @param string $attribute
  253. * @return bool
  254. */
  255. protected function validate_email($attribute)
  256. {
  257. return filter_var($this->attributes[$attribute], FILTER_VALIDATE_EMAIL) !== false;
  258. }
  259. /**
  260. * Validate than an attribute is a valid URL.
  261. *
  262. * @param string $attribute
  263. * @return bool
  264. */
  265. protected function validate_url($attribute)
  266. {
  267. return filter_var($this->attributes[$attribute], FILTER_VALIDATE_URL) !== false;
  268. }
  269. /**
  270. * Validate that an attribute is an active URL.
  271. *
  272. * @param string $attribute
  273. * @return bool
  274. */
  275. protected function validate_active_url($attribute)
  276. {
  277. $url = str_replace(array('http://', 'https://', 'ftp://'), '', Str::lower($this->attributes[$attribute]));
  278. return checkdnsrr($url);
  279. }
  280. /**
  281. * Validate the MIME type of a file is an image MIME type.
  282. *
  283. * @param string $attribute
  284. * @return bool
  285. */
  286. protected function validate_image($attribute)
  287. {
  288. return static::validate_mime($attribute, array('jpg', 'png', 'gif', 'bmp'));
  289. }
  290. /**
  291. * Validate than an attribute contains only alphabetic characters.
  292. *
  293. * @param string $attribute
  294. * @return bool
  295. */
  296. protected function validate_alpha($attribute)
  297. {
  298. return preg_match('/^([a-z])+$/i', $this->attributes[$attribute]);
  299. }
  300. /**
  301. * Validate than an attribute contains only alpha-numeric characters.
  302. *
  303. * @param string $attribute
  304. * @return bool
  305. */
  306. protected function validate_alpha_num($attribute)
  307. {
  308. return preg_match('/^([a-z0-9])+$/i', $this->attributes[$attribute]);
  309. }
  310. /**
  311. * Validate than an attribute contains only alpha-numeric characters, dashes, and underscores.
  312. *
  313. * @param string $attribute
  314. * @return bool
  315. */
  316. protected function validate_alpha_dash($attribute)
  317. {
  318. return preg_match('/^([-a-z0-9_-])+$/i', $this->attributes[$attribute]);
  319. }
  320. /**
  321. * Validate the MIME type of a file upload attribute is in a set of MIME types.
  322. *
  323. * @param string $attribute
  324. * @param array $parameters
  325. * @return bool
  326. */
  327. protected function validate_mimes($attribute, $parameters)
  328. {
  329. foreach ($parameters as $extension)
  330. {
  331. if (File::is($extension, $this->attributes[$attribute]['tmp_name']))
  332. {
  333. return true;
  334. }
  335. }
  336. return false;
  337. }
  338. /**
  339. * Get the proper error message for an attribute and rule.
  340. *
  341. * Developer specified attribute specific rules take first priority.
  342. * Developer specified error rules take second priority.
  343. *
  344. * If the message has not been specified by the developer, the default will be used
  345. * from the validation language file.
  346. *
  347. * @param string $attribute
  348. * @param string $rule
  349. * @return string
  350. */
  351. protected function get_message($attribute, $rule)
  352. {
  353. if (array_key_exists($attribute.'_'.$rule, $this->messages))
  354. {
  355. return $this->messages[$attribute.'_'.$rule];
  356. }
  357. elseif (array_key_exists($rule, $this->messages))
  358. {
  359. return $this->messages[$rule];
  360. }
  361. else
  362. {
  363. $message = Lang::line('validation.'.$rule)->get();
  364. // For "size" rules that are validating strings or files, we need to adjust
  365. // the default error message appropriately.
  366. if (in_array($rule, $this->size_rules) and ! $this->has_numeric_rule($attribute))
  367. {
  368. return (array_key_exists($attribute, $_FILES)) ? rtrim($message, '.').' kilobytes.' : rtrim($message, '.').' characters.';
  369. }
  370. return $message;
  371. }
  372. }
  373. /**
  374. * Replace all error message place-holders with actual values.
  375. *
  376. * @param string $message
  377. * @param string $attribute
  378. * @param string $rule
  379. * @param array $parameters
  380. * @return string
  381. */
  382. protected function format_message($message, $attribute, $rule, $parameters)
  383. {
  384. $display = Lang::line('attributes.'.$attribute)->get(null, function() use ($attribute) { return str_replace('_', ' ', $attribute); });
  385. $message = str_replace(':attribute', $display, $message);
  386. if (in_array($rule, $this->size_rules))
  387. {
  388. $max = ($rule == 'between') ? $parameters[1] : $parameters[0];
  389. $message = str_replace(':size', $parameters[0], str_replace(':min', $parameters[0], str_replace(':max', $max, $message)));
  390. }
  391. elseif (in_array($rule, array('in', 'not_in', 'mimes')))
  392. {
  393. $message = str_replace(':values', implode(', ', $parameters), $message);
  394. }
  395. return $message;
  396. }
  397. /**
  398. * Determine if an attribute has either a "numeric" or "integer" rule.
  399. *
  400. * @param string $attribute
  401. * @return bool
  402. */
  403. protected function has_numeric_rule($attribute)
  404. {
  405. return $this->has_rule($attribute, array('numeric', 'integer'));
  406. }
  407. /**
  408. * Determine if an attribute has a rule assigned to it.
  409. *
  410. * @param string $attribute
  411. * @param array $rules
  412. * @return bool
  413. */
  414. protected function has_rule($attribute, $rules)
  415. {
  416. foreach ($this->rules[$attribute] as $rule)
  417. {
  418. list($rule, $parameters) = $this->parse($rule);
  419. if (in_array($rule, $rules))
  420. {
  421. return true;
  422. }
  423. }
  424. return false;
  425. }
  426. /**
  427. * Extrac the rule name and parameters from a rule.
  428. *
  429. * @param string $rule
  430. * @return array
  431. */
  432. protected function parse($rule)
  433. {
  434. $parameters = (($colon = strpos($rule, ':')) !== false) ? explode(',', substr($rule, $colon + 1)) : array();
  435. return array(is_numeric($colon) ? substr($rule, 0, $colon) : $rule, $parameters);
  436. }
  437. }