Navigation: Validations | Conditional Validation | Rules & Schemas | Custom Strategies | Error Handling | Internationalization | Benchmarks
DataVerify supports conditional validation where validation rules are only applied when specific conditions are met. Combine conditions using and() and or() operators.
- Quick Start
- Basic Concepts
- Supported Operators
- AND / OR Conditions
- Nested Fields
- Restrictions
- Common Use Cases
- Best Practices
$data->delivery_type = "shipping";
$data->shipping_address = "";
$dv = new DataVerify($data);
$dv->field('shipping_address')
->when('delivery_type', '=', 'shipping')
->then->required->string;
// shipping_address only required when delivery_type equals 'shipping'Pattern:
$dv->field('target')
->when('condition_field', 'operator', 'value')
->then->validation_rules;when() and then
when() - Define condition(s) that must be met
then - Activate conditional mode (always required after when/and/or)
$dv->field('email')
->when('status', '=', 'active')
->then->required->email;
// email validated only when status equals 'active'With nested fields (dot notation):
$data->user = new stdClass();
$data->user->role = 'admin';
$dv->field('admin_key')
->when('user.role', '=', 'admin')
->then->required->string;| Operator | Description | Example |
|---|---|---|
= |
Equal (strict) | ->when('status', '=', 'active') |
!= |
Not equal (strict) | ->when('type', '!=', 'guest') |
< |
Less than | ->when('age', '<', 18) |
> |
Greater than | ->when('amount', '>', 100) |
<= |
Less than or equal | ->when('age', '<=', 65) |
>= |
Greater than or equal | ->when('age', '>=', 18) |
in |
In array (strict) | ->when('country', 'in', ['FR', 'DE']) |
not_in |
Not in array (strict) | ->when('role', 'not_in', ['admin']) |
All comparisons use strict mode (===, !==, in_array(..., true))
AND - All Must Be True
$dv->field('discount_code')
->when('type', '=', 'premium')
->and('amount', '>', 100)
->then->required->string;
// Required when type='premium' AND amount>100Multiple AND:
$dv->field('premium_feature')
->when('age', '>=', 18)
->and('country', 'in', ['FR', 'BE'])
->and('income', '>', 30000)
->then->required;
// All three conditions must be trueOR - At Least One Must Be True
$dv->field('vat_number')
->when('country', '=', 'FR')
->or('country', '=', 'BE')
->or('country', '=', 'DE')
->then->required->string;
// Required if country is FR OR BE OR DETip: Use in operator instead of multiple or():
// ✅ Cleaner
$dv->field('vat_number')
->when('country', 'in', ['FR', 'BE', 'DE'])
->then->required;Dot Notation for Nested Objects
$data->user = new stdClass();
$data->user->type = "business";
$data->user->country = "FR";
$dv = new DataVerify($data);
$dv->field('user')->required->object
->subfield('vat_number')
->when('user.type', '=', 'business')
->and('user.country', 'in', ['FR', 'DE', 'IT'])
->then->required->string;
// vat_number required when user.type='business' AND country is EUUse full path from root, not relative paths.
Conditional Block Terminators
New in v1.1.0: Conditional blocks are automatically terminated by specific actions, allowing cleaner syntax:
Terminators:
->when()- Starts new conditional block, terminates previous one->field()- Starts new field, terminates any active block->subfield()- Starts new subfield, terminates any active block
// Example: Multiple blocks on same field
$dv->field('password')
->minLength(8) // Unconditional
->when('user.id', '!=', null)
->then->required // Block 1
->when('user.role', '=', 'admin') // Terminates Block 1, starts Block 2
->then->minLength(16); // Block 2
// Example: Blocks across fields
$dv->field('email')
->when('user.id', '!=', null)
->then->required // Conditional on email
->field('phone') // Terminates block, starts new field
->required; // Unconditional on phone
// Example: Blocks across subfields
$dv->field('parent')->object
->subfield('sub1')
->when('x', '=', 1)
->then->required // Conditional on sub1
->subfield('sub2') // Terminates block
->string; // Unconditional on sub2No explicit terminator needed - blocks end naturally at field boundaries.
Cannot Mix AND/OR
You cannot mix and() and or() in the same chain:
// ❌ Invalid
->when('x', '=', 1)
->and('y', '>', 0)
->or('z', '<', 100) // Error!
->then->required
// ✅ Valid - All AND
->when('x', '=', 1)
->and('y', '>', 0)
->and('z', '<', 100)
->then->required
// ✅ Valid - All OR
->when('a', '=', 1)
->or('b', '=', 2)
->or('c', '=', 3)
->then->requiredWorkaround for complex logic (A AND B) OR C:
// Split into two separate validations
$dv->field('discount')
->when('type', '=', 'premium')
->and('amount', '>', 100)
->then->required;
$dv->field('discount')
->when('special_offer', '=', true)
->then->required;
// If EITHER condition is true, discount is requiredMust Use 'then' After Conditions
// ❌ Missing 'then'
->when('status', '=', 'active')
->required // Error!
// ✅ Correct
->when('status', '=', 'active')
->then->requiredAge-Based Validation
$dv->field('parental_consent')
->when('age', '<', 18)
->then->required->boolean;
// Consent required for users under 18Business Account
$dv->field('company_name')
->when('account_type', '=', 'business')
->then->required->string
->field('tax_id')
->when('account_type', '=', 'business')
->and('country', 'in', ['FR', 'DE', 'IT'])
->then->required->string;
// company_name required for all business accounts
// tax_id required for business accounts in EUShipping Logic
$dv->field('shipping_address')
->when('delivery_type', '=', 'shipping')
->then->required->string;
// Address only required when shipping (not pickup)Payment Validation
$dv->field('kyc_document')
->when('payment_method', 'in', ['crypto', 'wire_transfer'])
->then->required->string
->field('wallet_address')
->when('payment_method', '=', 'crypto')
->then->required->regex('/^0x[a-fA-F0-9]{40}$/');
// KYC for high-risk payments, wallet for cryptoAPI REST - POST vs PATCH
New in v1.1.0: Perfect for differentiating create (POST) vs update (PATCH) validation:
// POST /users - All fields required
$dv->field('user')->required->object
->subfield('email')->required->email
->subfield('password')->required->minLength(12)->containsUpper;
// PATCH /users/:id - Conditional validation
$dv->field('user')->object
->subfield('email')
->email // Format always checked if present
->when('user.id', '!=', null)
->then->required // Required only if updating
->subfield('password')
->minLength(12) // Constraints always checked if present
->containsUpper
->when('user.id', '!=', null)
->then->required; // Required only if updating
// In PATCH, fields are optional but must be valid if providedBonus - Role-based validation:
$dv->field('password')
->minLength(8) // All users
->containsUpper // All users
->when('user.role', '=', 'admin')
->then->minLength(16) // Admins need stronger passwords
->containsSpecialCharacter;Mix Normal and Conditional
New in v1.1.0: Conditional blocks now support deferred evaluation - conditions are evaluated during verify() rather than during construction. This enables more intuitive syntax where default rules come first, followed by conditional rules.
// Default rules + conditional rules
$dv->field('password')
->minLength(8) // ← Always required
->containsUpper // ← Always required
->when('user.id', '!=', null)
->then->required // ← Only when user exists
->minLength(12); // ← Only when user exists
// Multiple conditional blocks
$dv->field('password')
->minLength(8) // ← Always
->when('user.id', '!=', null)
->then->required // ← Block 1: if user exists
->when('user.role', '=', 'admin') // ← Block 2: if admin
->then->minLength(16) // ← Block 2: stronger requirements
->containsSpecialCharacter; // ← Block 2How conditional blocks work:
A conditional block starts with when() and includes all validations until:
- Another
when()is called (starts new block) - Another
field()orsubfield()is called (ends block) - End of chain (implicit termination)
Example - Separating blocks:
$dv->field('email')
->email // ← Unconditional
->when('user.id', '!=', null)
->then->required // ← Conditional block
->field('phone') // ← Terminates previous block
->regex('/^\+?[1-9]\d{1,14}$/'); // ← Unconditional on new fieldOld syntax still works:
// Traditional approach (still valid)
$dv->field('email')
->when('contact_preference', '=', 'email')
->then->required->email;
// All validations after 'then' are conditional✅ Keep conditions simple:
// Good - 1-2 conditions
->when('type', '=', 'premium')
->and('amount', '>', 100)
->then->required
// Too complex - split it
->when('account_type', '=', 'business')
->and('country', 'in', ['FR', 'DE', 'IT'])
->and('annual_revenue', '>', 100000)
->and('employees', '>', 50)
->then->required✅ Use in operator over multiple or():
// Bad - verbose
->when('country', '=', 'FR')
->or('country', '=', 'DE')
->or('country', '=', 'IT')
->then->required
// Good - clean
->when('country', 'in', ['FR', 'DE', 'IT'])
->then->required✅ Document complex business rules:
// EU VAT required for businesses >100k revenue
$dv->field('vat_number')
->when('account_type', '=', 'business')
->and('country', 'in', ['FR', 'DE', 'IT'])
->and('annual_revenue', '>', 100000)
->then->required->string;❌ Don't reference the field you're validating:
// Wrong - circular reference
->field('email')
->when('email', '=', 'admin@example.com')
->then->required
// Correct - reference other fields
->field('admin_access')
->when('email', '=', 'admin@example.com')
->then->required- Validation Rules - All available validation rules
- Rules & Schemas - Reusable validation patterns with conditionals
- Error Handling - Handle conditional validation errors
- Custom Strategies - Custom conditional logic
Navigation: Validations | Conditional Validation | Rules & Schemas | Custom Strategies | Error Handling | Internationalization | Benchmarks