<?php
|
|
namespace SparkLib;
|
|
|
|
use \Exception;
|
|
|
|
/**
|
|
* A general-purpose validation class.
|
|
*/
|
|
class Validator {
|
|
|
|
protected $_values;
|
|
protected $_exceptionClass = '\SparkLib\ValidatorException';
|
|
|
|
/**
|
|
* Build a new Validator, optionally specifying an exception class to throw
|
|
* on failure. The exception class's constructor is expected to take an
|
|
* array of failures, so if you need something besides the default
|
|
* ValidatorException, it would be reasonable to extend that one.
|
|
*
|
|
* This is all around a pretty bad design, and will eventually be refactored
|
|
* to remove the reliance on an exception for message-passing.
|
|
*
|
|
* @param $values array of key-value pairs to validate
|
|
* @param $exceptionClass optional string name of exception class to pass
|
|
* failure array
|
|
*/
|
|
public function __construct (array $values, $exceptionClass = null)
|
|
{
|
|
$this->_values = $values;
|
|
|
|
if (isset($exceptionClass)) {
|
|
$this->_exceptionClass = $exceptionClass;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Require parameters to be non-empty, and then check
|
|
* them against provided filters. A convenience wrapper around
|
|
* expect() and filter().
|
|
*/
|
|
public function expectFiltered (array $filters)
|
|
{
|
|
$failures = array();
|
|
|
|
// first, did we get all the desired fields?
|
|
try {
|
|
$this->expect(array_keys($filters));
|
|
} catch (Exception $e) {
|
|
$missing = $e->getErrors();
|
|
}
|
|
|
|
// second, do they pass the provided filters?
|
|
try {
|
|
$this->filter($filters);
|
|
} catch (Exception $e) {
|
|
$invalid = $e->getErrors();
|
|
}
|
|
|
|
if (isset($missing)) {
|
|
foreach ($missing as $field => $val)
|
|
$failures[$field] = $val;
|
|
}
|
|
|
|
if (isset($invalid)) {
|
|
foreach ($invalid as $field => $val)
|
|
$failures[$field] = $val;
|
|
}
|
|
|
|
if ($failures) {
|
|
$e_class = $this->_exceptionClass;
|
|
throw new $e_class($failures);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Require request parameters to be non-empty(). For example, in a
|
|
* Controller action:
|
|
*
|
|
* $this->req()->expect('foo', 'bar');
|
|
*
|
|
* @param mixed... array or variable length list of parameters to expect
|
|
* @throws \SparkLib\Application\RequestException
|
|
*/
|
|
public function expect ()
|
|
{
|
|
$args = func_get_args();
|
|
|
|
// DIRTY DIRTY HACK
|
|
if (is_array($args[0]))
|
|
$expectations = $args[0];
|
|
else
|
|
$expectations = $args;
|
|
|
|
// Check everything in requires to make sure we have a value
|
|
$failures = $this->checkParams($expectations, $this->_values);
|
|
|
|
if (count($failures) > 0) {
|
|
$e_class = $this->_exceptionClass;
|
|
throw new $e_class($failures);
|
|
}
|
|
}
|
|
|
|
private function checkParams ($expectation, $value)
|
|
{
|
|
$failures = [];
|
|
|
|
if (is_array($expectation)) {
|
|
foreach ($expectation as $key => $expected) {
|
|
$check = is_array($expected) ? $key : $expected;
|
|
$fail = $this->checkParams($expected, $value[$check]);
|
|
$failures = array_merge($failures, $fail);
|
|
}
|
|
} else if (!is_array($value) && !strlen(trim($value))) {
|
|
$failures[$expectation] = 'missing';
|
|
}
|
|
|
|
return $failures;
|
|
}
|
|
|
|
/**
|
|
* Match request parameters against filters.
|
|
*
|
|
* $filters = array(
|
|
* 'foo' => 'FILTER_VALIDATE_INT',
|
|
* 'bar' => '/^[a-z]+$/',
|
|
* 'baz' => function ($value) {
|
|
* if ($value == 'barf') {
|
|
* return false;
|
|
* }
|
|
* return true;
|
|
* }
|
|
* );
|
|
* $this->req()->filter($filters);
|
|
*
|
|
* A filter may be the name of a filter constant, a / delimited
|
|
* PCRE expression, or an anonymous function which takes one parameter
|
|
* and returns true to pass or false to fail.
|
|
*
|
|
* Like expect(), this will throw a \SparkLib\Application\RequestException if something
|
|
* doesn't match, because programmers suck at checking error conditions and
|
|
* I'd rather make failures obvious. Wrap it in a catch block if you want to
|
|
* handle things more gracefully than a "Something broke". RequestException
|
|
* provides getErrors(), which returns the array of validation errors.
|
|
*
|
|
* See also: http://www.php.net/manual/en/filter.filters.validate.php
|
|
*
|
|
* @param array filters to check against specific parameters
|
|
* @throws \SparkLib\Application\RequestException
|
|
*/
|
|
public function filter (array $filters)
|
|
{
|
|
$failures = array();
|
|
|
|
// Check every param in the request against any defined filters
|
|
foreach ($filters as $param => $filter)
|
|
{
|
|
// If we weren't given a value, don't filter it.
|
|
// (If you want to mandate that the value is set, use expect() or expectFiltered())
|
|
// There is a special case here for the string '0', which empty() considers empty.
|
|
// Let Lerdorf hope that I never encounter him in a dark alley.
|
|
if (empty($this->_values[$param]) && ($this->_values[$param] !== '0')) {
|
|
continue;
|
|
}
|
|
|
|
$errstring = 'invalid';
|
|
if (is_array($filter)) {
|
|
$errstring = $filter[1];
|
|
$filter = $filter[0];
|
|
}
|
|
|
|
if (is_string($filter)) {
|
|
|
|
if ($filter[0] === '/') // treat the filter as a regexp
|
|
{
|
|
// fail if no match:
|
|
if (! preg_match($filter, $this->_values[$param]))
|
|
$failures[$param] = $errstring;
|
|
}
|
|
// treat the filter as a builtin filter
|
|
elseif (filter_var($this->_values[$param], constant($filter)) === FALSE)
|
|
{
|
|
$failures[$param] = $errstring;
|
|
}
|
|
|
|
} elseif (is_callable($filter)) {
|
|
|
|
// treat the filter as a callback
|
|
if (! $filter($this->_values[$param]))
|
|
$failures[$param] = $errstring;
|
|
|
|
}
|
|
}
|
|
|
|
if (count($failures) > 0) {
|
|
$e_class = $this->_exceptionClass;
|
|
throw new $e_class($failures);
|
|
}
|
|
}
|
|
|
|
}
|