validator.php 20 KB

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