validator.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. <?php namespace Laravel\Validation;
  2. use Closure;
  3. use Laravel\IoC;
  4. use Laravel\Str;
  5. use Laravel\Lang;
  6. use Laravel\Input;
  7. use Laravel\Database\Manager as DB;
  8. class Validator {
  9. /**
  10. * The registered custom validators.
  11. *
  12. * @var array
  13. */
  14. protected static $validators = array();
  15. /**
  16. * The validation rules.
  17. *
  18. * @var array
  19. */
  20. protected $rules = array();
  21. /**
  22. * The validation messages.
  23. *
  24. * @var array
  25. */
  26. protected $messages = array();
  27. /**
  28. * The language that should be used when retrieving error messages.
  29. *
  30. * @var string
  31. */
  32. protected $language;
  33. /**
  34. * The size related validation rules.
  35. *
  36. * @var array
  37. */
  38. protected $size_rules = array('size', 'between', 'min', 'max');
  39. /**
  40. * The numeric related validation rules.
  41. *
  42. * @var array
  43. */
  44. protected $numeric_rules = array('numeric', 'integer');
  45. /**
  46. * The database connection that should be used by the validator.
  47. *
  48. * @var Database\Connection
  49. */
  50. public $connection;
  51. /**
  52. * The array being validated.
  53. *
  54. * @var array
  55. */
  56. public $attributes;
  57. /**
  58. * The post-validation error messages.
  59. *
  60. * @var Messages
  61. */
  62. public $errors;
  63. /**
  64. * Create a new validator instance.
  65. *
  66. * @param array $attributes
  67. * @param array $rules
  68. * @param array $messages
  69. * @return void
  70. */
  71. public function __construct($attributes, $rules, $messages = array())
  72. {
  73. foreach ($rules as $key => &$rule)
  74. {
  75. $rule = (is_string($rule)) ? explode('|', $rule) : $rule;
  76. }
  77. $this->rules = $rules;
  78. $this->messages = $messages;
  79. $this->attributes = $attributes;
  80. }
  81. /**
  82. * Create a new validator instance.
  83. *
  84. * @param array $attributes
  85. * @param array $rules
  86. * @param array $messages
  87. * @return Validator
  88. */
  89. public static function make($attributes, $rules, $messages = array())
  90. {
  91. return new static($attributes, $rules, $messages);
  92. }
  93. /**
  94. * Register a custom validator.
  95. *
  96. * @param string $name
  97. * @param Closure $validator
  98. * @return void
  99. */
  100. public static function register($name, $validator)
  101. {
  102. static::$validators[$name] = $validator;
  103. }
  104. /**
  105. * Validate the target array using the specified validation rules.
  106. *
  107. * @return bool
  108. */
  109. public function invalid()
  110. {
  111. return ! $this->valid();
  112. }
  113. /**
  114. * Validate the target array using the specified validation rules.
  115. *
  116. * @return bool
  117. */
  118. public function valid()
  119. {
  120. $this->errors = new Messages;
  121. foreach ($this->rules as $attribute => $rules)
  122. {
  123. foreach ($rules as $rule)
  124. {
  125. $this->check($attribute, $rule);
  126. }
  127. }
  128. return count($this->errors->messages) == 0;
  129. }
  130. /**
  131. * Evaluate an attribute against a validation rule.
  132. *
  133. * @param string $attribute
  134. * @param string $rule
  135. * @return void
  136. */
  137. protected function check($attribute, $rule)
  138. {
  139. list($rule, $parameters) = $this->parse($rule);
  140. if ( ! method_exists($this, $validator = 'validate_'.$rule) and ! isset(static::$validators[$rule]))
  141. {
  142. throw new \Exception("Validation rule [$rule] doesn't exist.");
  143. }
  144. // No validation will be run for attributes that do not exist unless the rule being validated
  145. // is "required" or "accepted". No other rules have implicit "required" checks.
  146. if ( ! $this->validate_required($attribute) and ! in_array($rule, array('required', 'accepted'))) return;
  147. if ( ! $this->$validator($attribute, $parameters, $this))
  148. {
  149. $message = $this->format_message($this->get_message($attribute, $rule), $attribute, $rule, $parameters);
  150. $this->errors->add($attribute, $message, $attribute, $rule, $parameters);
  151. }
  152. }
  153. /**
  154. * Validate that a required attribute exists in the attributes array.
  155. *
  156. * @param string $attribute
  157. * @return bool
  158. */
  159. protected function validate_required($attribute)
  160. {
  161. if ( ! array_key_exists($attribute, $this->attributes)) return false;
  162. if (is_string($this->attributes[$attribute]) and trim($this->attributes[$attribute]) === '') return false;
  163. return true;
  164. }
  165. /**
  166. * Validate that an attribute has a matching confirmation attribute.
  167. *
  168. * @param string $attribute
  169. * @return bool
  170. */
  171. protected function validate_confirmed($attribute)
  172. {
  173. $value = $this->attributes[$attribute];
  174. $confirmation = $this->attributes[$attribute.'_confirmation'];
  175. return array_key_exists($attribute.'_confirmation', $this->attributes) and $value == $confirmation;
  176. }
  177. /**
  178. * Validate that an attribute was "accepted".
  179. *
  180. * This validation rule implies the attribute is "required".
  181. *
  182. * @param string $attribute
  183. * @return bool
  184. */
  185. protected function validate_accepted($attribute)
  186. {
  187. $value = $this->attributes[$attribute];
  188. return $this->validate_required($attribute) and ($value == 'yes' or $value == '1');
  189. }
  190. /**
  191. * Validate that an attribute is numeric.
  192. *
  193. * @param string $attribute
  194. * @return bool
  195. */
  196. protected function validate_numeric($attribute)
  197. {
  198. return is_numeric($this->attributes[$attribute]);
  199. }
  200. /**
  201. * Validate that an attribute is an integer.
  202. *
  203. * @param string $attribute
  204. * @return bool
  205. */
  206. protected function validate_integer($attribute)
  207. {
  208. return filter_var($this->attributes[$attribute], FILTER_VALIDATE_INT) !== false;
  209. }
  210. /**
  211. * Validate the size of an attribute.
  212. *
  213. * @param string $attribute
  214. * @param array $parameters
  215. * @return bool
  216. */
  217. protected function validate_size($attribute, $parameters)
  218. {
  219. return $this->get_size($attribute) == $parameters[0];
  220. }
  221. /**
  222. * Validate the size of an attribute is between a set of values.
  223. *
  224. * @param string $attribute
  225. * @param array $parameters
  226. * @return bool
  227. */
  228. protected function validate_between($attribute, $parameters)
  229. {
  230. return $this->get_size($attribute) >= $parameters[0] and $this->get_size($attribute) <= $parameters[1];
  231. }
  232. /**
  233. * Validate the size of an attribute is greater than a minimum value.
  234. *
  235. * @param string $attribute
  236. * @param array $parameters
  237. * @return bool
  238. */
  239. protected function validate_min($attribute, $parameters)
  240. {
  241. return $this->get_size($attribute) >= $parameters[0];
  242. }
  243. /**
  244. * Validate the size of an attribute is less than a maximum value.
  245. *
  246. * @param string $attribute
  247. * @param array $parameters
  248. * @return bool
  249. */
  250. protected function validate_max($attribute, $parameters)
  251. {
  252. return $this->get_size($attribute) <= $parameters[0];
  253. }
  254. /**
  255. * Get the size of an attribute.
  256. *
  257. * @param string $attribute
  258. * @return mixed
  259. */
  260. protected function get_size($attribute)
  261. {
  262. if (is_numeric($this->attributes[$attribute]) and $this->has_rule($attribute, $this->numeric_rules))
  263. {
  264. return $this->attributes[$attribute];
  265. }
  266. $value = $this->attributes[$attribute];
  267. return (array_key_exists($attribute, Input::file())) ? $value['size'] / 1024 : Str::length(trim($value));
  268. }
  269. /**
  270. * Validate an attribute is contained within a list of values.
  271. *
  272. * @param string $attribute
  273. * @param array $parameters
  274. * @return bool
  275. */
  276. protected function validate_in($attribute, $parameters)
  277. {
  278. return in_array($this->attributes[$attribute], $parameters);
  279. }
  280. /**
  281. * Validate an attribute is not contained within a list of values.
  282. *
  283. * @param string $attribute
  284. * @param array $parameters
  285. * @return bool
  286. */
  287. protected function validate_not_in($attribute, $parameters)
  288. {
  289. return ! in_array($this->attributes[$attribute], $parameters);
  290. }
  291. /**
  292. * Validate the uniqueness of an attribute value on a given database table.
  293. *
  294. * If a database column is not specified, the attribute name will be used.
  295. *
  296. * @param string $attribute
  297. * @param array $parameters
  298. * @return bool
  299. */
  300. protected function validate_unique($attribute, $parameters)
  301. {
  302. if ( ! isset($parameters[1])) $parameters[1] = $attribute;
  303. if (is_null($this->connection)) $this->connection = DB::connection();
  304. return $this->connection->table($parameters[0])->where($parameters[1], '=', $this->attributes[$attribute])->count() == 0;
  305. }
  306. /**
  307. * Validate than an attribute is a valid e-mail address.
  308. *
  309. * @param string $attribute
  310. * @return bool
  311. */
  312. protected function validate_email($attribute)
  313. {
  314. return filter_var($this->attributes[$attribute], FILTER_VALIDATE_EMAIL) !== false;
  315. }
  316. /**
  317. * Validate than an attribute is a valid URL.
  318. *
  319. * @param string $attribute
  320. * @return bool
  321. */
  322. protected function validate_url($attribute)
  323. {
  324. return filter_var($this->attributes[$attribute], FILTER_VALIDATE_URL) !== false;
  325. }
  326. /**
  327. * Validate that an attribute is an active URL.
  328. *
  329. * @param string $attribute
  330. * @return bool
  331. */
  332. protected function validate_active_url($attribute)
  333. {
  334. $url = str_replace(array('http://', 'https://', 'ftp://'), '', Str::lower($this->attributes[$attribute]));
  335. return checkdnsrr($url);
  336. }
  337. /**
  338. * Validate the MIME type of a file is an image MIME type.
  339. *
  340. * @param string $attribute
  341. * @return bool
  342. */
  343. protected function validate_image($attribute)
  344. {
  345. return $this->validate_mimes($attribute, array('jpg', 'png', 'gif', 'bmp'));
  346. }
  347. /**
  348. * Validate than an attribute contains only alphabetic characters.
  349. *
  350. * @param string $attribute
  351. * @return bool
  352. */
  353. protected function validate_alpha($attribute)
  354. {
  355. return preg_match('/^([a-z])+$/i', $this->attributes[$attribute]);
  356. }
  357. /**
  358. * Validate than an attribute contains only alpha-numeric characters.
  359. *
  360. * @param string $attribute
  361. * @return bool
  362. */
  363. protected function validate_alpha_num($attribute)
  364. {
  365. return preg_match('/^([a-z0-9])+$/i', $this->attributes[$attribute]);
  366. }
  367. /**
  368. * Validate than an attribute contains only alpha-numeric characters, dashes, and underscores.
  369. *
  370. * @param string $attribute
  371. * @return bool
  372. */
  373. protected function validate_alpha_dash($attribute)
  374. {
  375. return preg_match('/^([-a-z0-9_-])+$/i', $this->attributes[$attribute]);
  376. }
  377. /**
  378. * Validate the MIME type of a file upload attribute is in a set of MIME types.
  379. *
  380. * @param string $attribute
  381. * @param array $parameters
  382. * @return bool
  383. */
  384. protected function validate_mimes($attribute, $parameters)
  385. {
  386. foreach ($parameters as $extension)
  387. {
  388. if (File::is($extension, $this->attributes[$attribute]['tmp_name'])) return true;
  389. }
  390. return false;
  391. }
  392. /**
  393. * Get the proper error message for an attribute and rule.
  394. *
  395. * Developer specified attribute specific rules take first priority.
  396. * Developer specified error rules take second priority.
  397. *
  398. * If the message has not been specified by the developer, the default
  399. * will be used from the validation language file.
  400. *
  401. * @param string $attribute
  402. * @param string $rule
  403. * @return string
  404. */
  405. protected function get_message($attribute, $rule)
  406. {
  407. if (array_key_exists($attribute.'_'.$rule, $this->messages))
  408. {
  409. return $this->messages[$attribute.'_'.$rule];
  410. }
  411. elseif (array_key_exists($rule, $this->messages))
  412. {
  413. return $this->messages[$rule];
  414. }
  415. else
  416. {
  417. $message = Lang::line('validation.'.$rule)->get($this->language);
  418. // For "size" rules that are validating strings or files, we need to adjust
  419. // the default error message for the appropriate units.
  420. if (in_array($rule, $this->size_rules) and ! $this->has_rule($attribute, $this->numeric_rules))
  421. {
  422. return (array_key_exists($attribute, Input::file()))
  423. ? rtrim($message, '.').' '.Lang::line('validation.kilobytes')->get($this->language).'.'
  424. : rtrim($message, '.').' '.Lang::line('validation.characters')->get($this->language).'.';
  425. }
  426. return $message;
  427. }
  428. }
  429. /**
  430. * Replace all error message place-holders with actual values.
  431. *
  432. * @param string $message
  433. * @param string $attribute
  434. * @param string $rule
  435. * @param array $parameters
  436. * @return string
  437. */
  438. protected function format_message($message, $attribute, $rule, $parameters)
  439. {
  440. // First we will get the language line for the attribute being validated.
  441. // Storing attribute names in a validation file allows the easily replacement
  442. // of attribute names (email) with more reader friendly versions (E-Mail).
  443. $display = Lang::line('validation.attributes.'.$attribute)->get($this->language, str_replace('_', ' ', $attribute));
  444. $message = str_replace(':attribute', $display, $message);
  445. // The "size" family of rules all have place-holders for the values applicable
  446. // to their function. For example, the "max" rule has a ":max" place-holder.
  447. if (in_array($rule, $this->size_rules))
  448. {
  449. $max = ($rule == 'between') ? $parameters[1] : $parameters[0];
  450. $message = str_replace(array(':size', ':min', ':max'), array($parameters[0], $parameters[0], $max), $message);
  451. }
  452. // The "inclusion" rules, which are rules that check if a value is within
  453. // a list of values, all have a place-holder to display the allowed values.
  454. elseif (in_array($rule, array('in', 'not_in', 'mimes')))
  455. {
  456. $message = str_replace(':values', implode(', ', $parameters), $message);
  457. }
  458. return $message;
  459. }
  460. /**
  461. * Determine if an attribute has a rule assigned to it.
  462. *
  463. * @param string $attribute
  464. * @param array $rules
  465. * @return bool
  466. */
  467. protected function has_rule($attribute, $rules)
  468. {
  469. foreach ($this->rules[$attribute] as $rule)
  470. {
  471. list($rule, $parameters) = $this->parse($rule);
  472. if (in_array($rule, $rules)) return true;
  473. }
  474. return false;
  475. }
  476. /**
  477. * Extract the rule name and parameters from a rule.
  478. *
  479. * @param string $rule
  480. * @return array
  481. */
  482. protected function parse($rule)
  483. {
  484. // The format for specifying validation rules and parameters follows a {rule}:{parameters}
  485. // convention. For instance, "max:3" specifies that the value may only be 3 characters in
  486. // length. And "unique:users" specifies that a value must be unique on the "users" table.
  487. $parameters = (($colon = strpos($rule, ':')) !== false) ? explode(',', substr($rule, $colon + 1)) : array();
  488. return array(is_numeric($colon) ? substr($rule, 0, $colon) : $rule, $parameters);
  489. }
  490. /**
  491. * Set the language that should be used when retrieving error messages.
  492. *
  493. * @param string $language
  494. * @return Validator
  495. */
  496. public function speaks($language)
  497. {
  498. $this->language = $language;
  499. return $this;
  500. }
  501. /**
  502. * Set the database connection that should be used by the validator.
  503. *
  504. * @param Database\Connection $connection
  505. * @return Validator
  506. */
  507. public function connection(\Laravel\Database\Connection $connection)
  508. {
  509. $this->connection = $connection;
  510. return $this;
  511. }
  512. /**
  513. * Dynamically handle calls to custom registered validators.
  514. */
  515. public function __call($method, $parameters)
  516. {
  517. // First we will slice the "validate_" prefix off of the validator since custom
  518. // validators are not registered with such a prefix. We will then call the validator
  519. // and pass it the parameters we received.
  520. if (isset(static::$validators[$method = substr($method, 9)]))
  521. {
  522. return call_user_func_array(static::$validators[$method], $parameters);
  523. }
  524. throw new \Exception("Call to undefined method [$method] on Validator instance.");
  525. }
  526. }