validator.php 16 KB

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