PHP Classes

How to Use a PHP Validation Library that Check if Related Values Are Valid Using the Package Contextual Validator - Cross-Field Coherence Checker: Validate multiple form fields that are related

Recommend this page to a friend!
  Info   Example   Screenshots   View files Files   Install with Composer Install with Composer   Download Download   Reputation   Support forum   Blog    
Last Updated Ratings Unique User Downloads Download Rankings
2026-08-03 (11 days ago) RSS 2.0 feedNot yet rated by the usersTotal: Not yet counted Not yet ranked
Version License PHP version Categories
contextual-validator 1.0MIT/X Consortium ...7.4Validation, PHP 7
Description 

Author

This package can validate multiple form fields that are related.

It provides a class that can take an array submitted form values and can perform several types of validation that check the values of multiple related values.

Currently it can validate:

- Zip codes of a country

- Start and end rates of a period

- Passwords and confirmation passwords

- Minimum and maximum prices

- Age and birth dates consistency

- Phone number and countries

- Start and end numbers

Picture of AKONO Metsam Nathan
Name: AKONO Metsam Nathan <contact>
Classes: 2 packages by
Country: Cameroon Cameroon
Innovation award
Innovation award
Nominee: 1x

Instructions

Instalation

Copy src/ContextualValidator.php into your project and require it - no Composer, no dependencies.

require 'ContextualValidator.php';

use ContextValidate\ContextualValidator;

Basic Usage

Create a validator with your submitted data (typically $_POST), then chain the rules you need:

$validator = new ContextualValidator($_POST);

$validator
    ->postalCodeMatchesCountry('zip', 'country')
    ->dateRange('start_date', 'end_date')
    ->ageMatchesBirthdate('age', 'birthdate')
    ->phoneMatchesCountry('phone', 'country')
    ->fieldsMatch('password', 'password_confirmation')
    ->numericRange('min_price', 'max_price');

if ($validator->fails()) {
    foreach ($validator->errors() as $field => $messages) {
        foreach ($messages as $message) {
            echo "$field: $message\n";
        }
    }
}

Avaliable Rules

postalCodeMatchesCountry($postalField, $countryField)

Checks the postal code format against the ISO country code
(25+ countries built in: US, CA, GB, FR, DE, ES, IT, NL, BE, CH,
AT, PT, AU, JP, BR, IN, CN, MX, SE, NO, DK, FI, PL, IE...).

dateRange($startField, $endField, $allowEqual = true)

Checks that the start date is not after the end date. Pass
false as the third argument to reject equal dates too.

ageMatchesBirthdate($ageField, $birthdateField, $toleranceYears = 0, $referenceDate = null)

Checks that a declared age is consistent with a birthdate.
Use $toleranceYears to allow a small margin.

phoneMatchesCountry($phoneField, $countryField)

Checks that a phone number's digit count is plausible for the
given country. This is a lightweight sanity check, not full
phone number validation.

fieldsMatch($fieldA, $fieldB, $message = null)

Checks that two fields hold identical values - typical use:
password / password confirmation.

numericRange($minField, $maxField)

Checks that a "min" field is not greater than a "max" field.

custom($field, $callable)

Add your own cross-field rule. The callable receives the full
data array and must return true, or a string error message:

    $validator->custom('discount', function (array $data) {
        return $data['discount'] <= $data['price']
            ? true
            : 'Discount cannot exceed price.';
    });

Extending Country Coverage

To add or override a postal code pattern for a country not in the built-in list:

ContextualValidator::addPostalCodePattern('XX', '/^\d{5}$/');

Reading Results

$validator->passes();       // bool
$validator->fails();        // bool
$validator->errors();       // array<string, string[]> keyed by field
$validator->allMessages();  // string[] flattened, in rule order

Important Notes

  • Every rule skips silently (adds no error) when a required field is empty, or when the country isn't in the built-in postal/phone list. This class only checks CONSISTENCY between fields - pair it with your usual field-level validator (or required-field checks) for presence/format validation.
  • Phone validation checks digit count plausibility only, not real numbering-plan rules. For full phone validation, use a dedicated library (e.g. a libphonenumber port) and plug it in via custom().

Running the Tests

php tests/run-tests.php

27 assertions, no external test framework required.

Example

<?php

declare(strict_types=1);

require
__DIR__ . '/../src/ContextualValidator.php';

use
ContextValidate\ContextualValidator;

// Simulate a submitted registration + booking form. Every individual
// field below is well-formed on its own - an ordinary validator would
// let all of this through. The inconsistencies only show up when the
// fields are checked against each other.
$submitted = [
   
'zip' => '90210-1234', // US-style zip...
   
'country' => 'FR', // ...but country is France
   
'phone' => '212-555-0100',
   
'start_date' => '2026-08-10',
   
'end_date' => '2026-08-01', // end before start
   
'age' => '25',
   
'birthdate' => '1990-03-14', // implies age ~36, not 25
   
'password' => 'hunter2',
   
'password_confirmation' => 'hunter3', // typo
   
'min_price' => '200',
   
'max_price' => '50', // filter inverted
];

$validator = new ContextualValidator($submitted);

$validator
   
->postalCodeMatchesCountry('zip', 'country')
    ->
phoneMatchesCountry('phone', 'country')
    ->
dateRange('start_date', 'end_date')
    ->
ageMatchesBirthdate('age', 'birthdate')
    ->
fieldsMatch('password', 'password_confirmation')
    ->
numericRange('min_price', 'max_price');

if (
$validator->fails()) {
    echo
"Form has cross-field inconsistencies:\n\n";
    foreach (
$validator->errors() as $field => $messages) {
        foreach (
$messages as $message) {
            echo
" [{$field}] {$message}\n";
        }
    }
} else {
    echo
"All good!\n";
}


Details

Contextual Validator ? Cross-Field Coherence Checker for PHP

Most PHP validation libraries check one field at a time: is this a valid email, is this string non-empty, is this number in range. That catches typos, but it misses an entire class of bugs where each field is individually valid, yet the form as a whole is inconsistent.

This class validates relationships between fields instead of fields in isolation.

The problem

All of these pass every ordinary field-by-field validator, yet every one of them is wrong:

  • postal code `90210` submitted with country `France`
  • an `end_date` earlier than `start_date`
  • a `birthdate` of 1990 combined with a declared `age` of 15
  • a phone number with a US-length number paired with `country = Germany`
  • a `password_confirmation` that doesn't match `password`
  • a `min_price` filter greater than the `max_price` filter

Installation

Copy src/ContextualValidator.php into your project, or require it directly. No dependencies.

Usage

require 'src/ContextualValidator.php';

use ContextValidate\ContextualValidator;

$validator = new ContextualValidator($_POST);

$validator
    ->postalCodeMatchesCountry('zip', 'country')
    ->dateRange('start_date', 'end_date')
    ->ageMatchesBirthdate('age', 'birthdate')
    ->phoneMatchesCountry('phone', 'country')
    ->fieldsMatch('password', 'password_confirmation')
    ->numericRange('min_price', 'max_price');

if ($validator->fails()) {
    foreach ($validator->errors() as $field => $messages) {
        echo "$field: " . implode(', ', $messages) . "\n";
    }
}

See examples/example.php for a full runnable demo with a form that fails every single rule at once.

Built-in rules

| Method | Checks | |---|---| | postalCodeMatchesCountry($postalField, $countryField) | Postal code format matches the ISO country code (25+ countries built in) | | dateRange($startField, $endField, $allowEqual = true) | Start date is not after end date | | ageMatchesBirthdate($ageField, $birthdateField, $toleranceYears = 0, $referenceDate = null) | Declared age matches computed age from birthdate | | phoneMatchesCountry($phoneField, $countryField) | Phone number digit count is plausible for the given country | | fieldsMatch($fieldA, $fieldB, $message = null) | Two fields hold identical values (e.g. password confirmation) | | numericRange($minField, $maxField) | A "min" field is not greater than a "max" field | | custom($field, $callable) | Any custom cross-field rule; return true or an error string |

Every rule skips silently (does not add an error) when a required field is empty or when the country isn't in the built-in list - it's not this class's job to require fields or judge unknown countries; pair it with your usual field-level validator for that.

Extending country coverage

ContextualValidator::addPostalCodePattern('XX', '/^\d{5}$/');

Limitations

  • Phone validation checks digit count plausibility only, not real numbering-plan rules. For full phone number validation, pair this with a dedicated library (e.g. a libphonenumber port) and use `custom()` to plug it in as a cross-field rule.
  • Postal code coverage is currently ~25 countries; unknown countries are skipped rather than flagged, by design.

Tests

php tests/run-tests.php

License

MIT ? see LICENSE.


Screenshots (1)  
  • contextual-validator.png
  Files folder image Files (6)  
File Role Description
Files folder imagesrc (1 file)
Files folder imageexamples (1 file)
Files folder imagetests (1 file)
Accessible without login Plain text file README.md Doc. Package documentation: problem statement, usage, full API reference, limitations.
Accessible without login Plain text file LICENSE Lic. MIT License text.

  Files folder image Files (6)  /  src  
File Role Description
  Plain text file ContextualValidator.php Class Main class: validates relationships between form fields (postal code/country, date ranges, age/birthdate, phone/country, field matching, numeric ranges).

  Files folder image Files (6)  /  examples  
File Role Description
  Accessible without login Plain text file example.php Example Full working example: a form that fails all 5 rules at once, demonstrating every cross-field check together.

  Files folder image Files (6)  /  tests  
File Role Description
  Accessible without login Plain text file run-tests.php Example Dependency-free automated test suite (27 assertions) covering every rule.

The PHP Classes site has supported package installation using the Composer tool since 2013, as you may verify by reading this instructions page.
Install with Composer Install with Composer
 Version Control Unique User Downloads  
 0%
Total:0
This week:0