validator.php 16 KB

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