A modest collection of PHP libraries used at SparkFun.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

200 lines
5.6 KiB

  1. <?php
  2. namespace SparkLib;
  3. use \Exception;
  4. /**
  5. * A general-purpose validation class.
  6. */
  7. class Validator {
  8. protected $_values;
  9. protected $_exceptionClass = '\SparkLib\ValidatorException';
  10. /**
  11. * Build a new Validator, optionally specifying an exception class to throw
  12. * on failure. The exception class's constructor is expected to take an
  13. * array of failures, so if you need something besides the default
  14. * ValidatorException, it would be reasonable to extend that one.
  15. *
  16. * This is all around a pretty bad design, and will eventually be refactored
  17. * to remove the reliance on an exception for message-passing.
  18. *
  19. * @param $values array of key-value pairs to validate
  20. * @param $exceptionClass optional string name of exception class to pass
  21. * failure array
  22. */
  23. public function __construct (array $values, $exceptionClass = null)
  24. {
  25. $this->_values = $values;
  26. if (isset($exceptionClass)) {
  27. $this->_exceptionClass = $exceptionClass;
  28. }
  29. }
  30. /**
  31. * Require parameters to be non-empty, and then check
  32. * them against provided filters. A convenience wrapper around
  33. * expect() and filter().
  34. */
  35. public function expectFiltered (array $filters)
  36. {
  37. $failures = array();
  38. // first, did we get all the desired fields?
  39. try {
  40. $this->expect(array_keys($filters));
  41. } catch (Exception $e) {
  42. $missing = $e->getErrors();
  43. }
  44. // second, do they pass the provided filters?
  45. try {
  46. $this->filter($filters);
  47. } catch (Exception $e) {
  48. $invalid = $e->getErrors();
  49. }
  50. if (isset($missing)) {
  51. foreach ($missing as $field => $val)
  52. $failures[$field] = $val;
  53. }
  54. if (isset($invalid)) {
  55. foreach ($invalid as $field => $val)
  56. $failures[$field] = $val;
  57. }
  58. if ($failures) {
  59. $e_class = $this->_exceptionClass;
  60. throw new $e_class($failures);
  61. }
  62. }
  63. /**
  64. * Require request parameters to be non-empty(). For example, in a
  65. * Controller action:
  66. *
  67. * $this->req()->expect('foo', 'bar');
  68. *
  69. * @param mixed... array or variable length list of parameters to expect
  70. * @throws \SparkLib\Application\RequestException
  71. */
  72. public function expect ()
  73. {
  74. $args = func_get_args();
  75. // DIRTY DIRTY HACK
  76. if (is_array($args[0]))
  77. $expectations = $args[0];
  78. else
  79. $expectations = $args;
  80. // Check everything in requires to make sure we have a value
  81. $failures = $this->checkParams($expectations, $this->_values);
  82. if (count($failures) > 0) {
  83. $e_class = $this->_exceptionClass;
  84. throw new $e_class($failures);
  85. }
  86. }
  87. private function checkParams ($expectation, $value)
  88. {
  89. $failures = [];
  90. if (is_array($expectation)) {
  91. foreach ($expectation as $key => $expected) {
  92. $check = is_array($expected) ? $key : $expected;
  93. $fail = $this->checkParams($expected, $value[$check]);
  94. $failures = array_merge($failures, $fail);
  95. }
  96. } else if (!is_array($value) && !strlen(trim($value))) {
  97. $failures[$expectation] = 'missing';
  98. }
  99. return $failures;
  100. }
  101. /**
  102. * Match request parameters against filters.
  103. *
  104. * $filters = array(
  105. * 'foo' => 'FILTER_VALIDATE_INT',
  106. * 'bar' => '/^[a-z]+$/',
  107. * 'baz' => function ($value) {
  108. * if ($value == 'barf') {
  109. * return false;
  110. * }
  111. * return true;
  112. * }
  113. * );
  114. * $this->req()->filter($filters);
  115. *
  116. * A filter may be the name of a filter constant, a / delimited
  117. * PCRE expression, or an anonymous function which takes one parameter
  118. * and returns true to pass or false to fail.
  119. *
  120. * Like expect(), this will throw a \SparkLib\Application\RequestException if something
  121. * doesn't match, because programmers suck at checking error conditions and
  122. * I'd rather make failures obvious. Wrap it in a catch block if you want to
  123. * handle things more gracefully than a "Something broke". RequestException
  124. * provides getErrors(), which returns the array of validation errors.
  125. *
  126. * See also: http://www.php.net/manual/en/filter.filters.validate.php
  127. *
  128. * @param array filters to check against specific parameters
  129. * @throws \SparkLib\Application\RequestException
  130. */
  131. public function filter (array $filters)
  132. {
  133. $failures = array();
  134. // Check every param in the request against any defined filters
  135. foreach ($filters as $param => $filter)
  136. {
  137. // If we weren't given a value, don't filter it.
  138. // (If you want to mandate that the value is set, use expect() or expectFiltered())
  139. // There is a special case here for the string '0', which empty() considers empty.
  140. // Let Lerdorf hope that I never encounter him in a dark alley.
  141. if (empty($this->_values[$param]) && ($this->_values[$param] !== '0')) {
  142. continue;
  143. }
  144. $errstring = 'invalid';
  145. if (is_array($filter)) {
  146. $errstring = $filter[1];
  147. $filter = $filter[0];
  148. }
  149. if (is_string($filter)) {
  150. if ($filter[0] === '/') // treat the filter as a regexp
  151. {
  152. // fail if no match:
  153. if (! preg_match($filter, $this->_values[$param]))
  154. $failures[$param] = $errstring;
  155. }
  156. // treat the filter as a builtin filter
  157. elseif (filter_var($this->_values[$param], constant($filter)) === FALSE)
  158. {
  159. $failures[$param] = $errstring;
  160. }
  161. } elseif (is_callable($filter)) {
  162. // treat the filter as a callback
  163. if (! $filter($this->_values[$param]))
  164. $failures[$param] = $errstring;
  165. }
  166. }
  167. if (count($failures) > 0) {
  168. $e_class = $this->_exceptionClass;
  169. throw new $e_class($failures);
  170. }
  171. }
  172. }