The heavy lifting for this recipe has already been accomplished in the preceding recipe. Core functionality is defined by Application\Filter\AbstractFilter. The actual validation is performed by an array of validating callbacks.
Application\Filter\CallbackInterface, and should return an instance of Application\Filter\Result. Validators would take this generic form:use Application\Filter\ { Result, Messages, CallbackInterface };
$config = [
// validator callbacks
'validators' => [
'key' => new class () implements CallbackInterface
{
public function __invoke($item, $params) : Result
{
// validation logic goes here
return new Result($valid, $error);
}
},
// etc.Application\Filter\Validator class, which loops through the array of assignments, testing each data item against its assigned validator callbacks. We make this class extend AbstractFilter in order to provide the core functionality described previously:namespace Application\Filter;
class Validator extends AbstractFilter
{
// code
}process() method that scans an array of data and applies validators as per the array of assignments. If there are no assigned validators for this data set, we simply return the current status of $valid (which is TRUE):public function process(array $data)
{
$valid = TRUE;
if (!(isset($this->assignments)
&& count($this->assignments))) {
return $valid;
}$this->results to an array of Result objects where the $item property is set to TRUE, and the $messages property is an empty array:foreach ($data as $key => $value) {
$this->results[$key] = new Result(TRUE, array());
}$this->assignments and check to see if there are any global filters (identified by the '*' key). If so, we run processGlobal() and then unset the '*' key:$toDo = $this->assignments;
if (isset($toDo['*'])) {
$this->processGlobalAssignment($toDo['*'], $data);
unset($toDo['*']);
}processAssignment(). This is an ideal place to check to see if any fields present in the assignments array is missing from the data. Note that we set $valid to FALSE if any validation callback returns FALSE:foreach ($toDo as $key => $assignment) {
if (!isset($data[$key])) {
$this->results[$key] =
new Result(FALSE, $this->missingMessage);
} else {
$this->processAssignment(
$assignment, $key, $data[$key]);
}
if (!$this->results[$key]->item) $valid = FALSE;
}
return $valid;
}processGlobalAssignment(), we need to loop through the array of callbacks. In this case, however, because these assignments are global, we also need to loop through the entire data set, and apply each global filter in turn.Application\Filter\Fiter::processGlobalAssignment() method, we need to call mergeValidationResults(). The reason for this is that if the value of $result->item is already FALSE, we need to ensure that it does not subsequently get overwritten by a value of TRUE. Any validator in the chain that returns FALSE must overwrite any other validation result:protected function processGlobalAssignment($assignment, $data)
{
foreach ($assignment as $callback) {
if ($callback === NULL) continue;
foreach ($data as $k => $value) {
$result = $this->callbacks[$callback['key']]
($value, $callback['params']);
$this->results[$k]->mergeValidationResults($result);
}
}
}processAssignment(), in a manner akin to processGlobalAssignment(), we need to execute each remaining callback assigned to each data key, again calling mergeValidationResults():protected function processAssignment($assignment, $key, $value)
{
foreach ($assignment as $callback) {
if ($callback === NULL) continue;
$result = $this->callbacks[$callback['key']]
($value, $callback['params']);
$this->results[$key]->mergeValidationResults($result);
}
}As with the preceding recipe, be sure to define the following classes:
Application\Filter\ResultApplication\Filter\CallbackInterfaceApplication\Filter\MessagesApplication\Filter\AbstractFilterYou can use the chap_06_post_data_config_messages.php file, also described in the previous recipe.
Next, create a Validator.php file in the Application\Filter folder. Place the code described in step 3 to 10.
Next, create a chap_06_post_data_config_callbacks.php callback configuration file that contains configurations for validation callbacks, as described in step 2. Each callback should follow this generic template:
'validation_key' => new class () implements CallbackInterface
{
public function __invoke($item, $params) : Result
{
$error = array();
$valid = /* perform validation operation on $item */
if (!$valid)
$error[] = Messages::$messages['validation_key'];
return new Result($valid, $error);
}
}Now you can create a chap_06_post_data_validation.php calling script that initializes autoloading and includes the configuration scripts:
<?php require __DIR__ . '/../Application/Autoload/Loader.php'; Application\Autoload\Loader::init(__DIR__ . '/..'); include __DIR__ . '/chap_06_post_data_config_messages.php'; include __DIR__ . '/chap_06_post_data_config_callbacks.php';
Next, define an array of assignments, mapping data fields to validator callback keys:
$assignments = [
'first_name' => [ ['key' => 'length',
'params' => ['min' => 1, 'max' => 128]],
['key' => 'alnum',
'params' => ['allowWhiteSpace' => TRUE]],
['key' => 'required','params' => []] ],
'last_name'=> [ ['key' => 'length',
'params' => ['min' => 1, 'max' => 128]],
['key' => 'alnum',
'params' => ['allowWhiteSpace' => TRUE]],
['key' => 'required','params' => []] ],
'address' => [ ['key' => 'length',
'params' => ['max' => 256]] ],
'city' => [ ['key' => 'length',
'params' => ['min' => 1, 'max' => 64]] ],
'state_province'=> [ ['key' => 'length',
'params' => ['min' => 1, 'max' => 32]] ],
'postal_code' => [ ['key' => 'length',
'params' => ['min' => 1, 'max' => 16] ],
['key' => 'alnum',
'params' => ['allowWhiteSpace' => TRUE]],
['key' => 'required','params' => []] ],
'phone' => [ ['key' => 'phone', 'params' => []] ],
'country' => [ ['key' => 'in_array',
'params' => $countries ],
['key' => 'required','params' => []] ],
'email' => [ ['key' => 'email', 'params' => [] ],
['key' => 'length',
'params' => ['max' => 250] ],
['key' => 'required','params' => [] ] ],
'budget' => [ ['key' => 'float', 'params' => []] ]
];For test data, use the same good and bad data defined in the chap_06_post_data_filtering.php file described in the previous recipe. After that, you are in a position to create an Application\Filter\Validator instance, and test the data:
$validator = new Application\Filter\Validator($config['validators'], $assignments); $validator->setSeparator(PHP_EOL); $validator->process($badData); echo $validator->getMessageString(40, '%14s : %-26s' . PHP_EOL); var_dump($validator->getItemsAsArray()); $validator->process($goodData); echo $validator->getMessageString(40, '%14s : %-26s' . PHP_EOL); var_dump($validator->getItemsAsArray());
As expected, the good data does not produce any validation errors. The bad data, on the other hand, generates the following output:

Notice that the missing fields, address and state_province validate FALSE, and return the missing item message.