Custom Rules
Valicomb makes it easy to add custom validation rules - either as one-time closures or as reusable global rules.
Closure-Based Rules
Section titled “Closure-Based Rules”Closure-based rules are perfect for one-off validations that are specific to a single form or use case. They’re defined inline and don’t need to be registered globally.
Basic Usage
Section titled “Basic Usage”Add a validation rule inline using a closure:
use Frostybee\Valicomb\Validator;
$v = new Validator(['username' => 'admin']);
$v->rule(function($field, $value, $params, $fields) { return $value !== 'admin';}, 'username')->message('Username cannot be "admin"');Callback Signature
Section titled “Callback Signature”function(string $field, mixed $value, array $params, array $fields): bool| Parameter | Description |
|---|---|
$field | The field name being validated |
$value | The current value of the field |
$params | Additional parameters passed to the rule |
$fields | All field data (for cross-field validation) |
| Return | true if valid, false if invalid |
Example with Parameters
Section titled “Example with Parameters”$v->rule(function($field, $value, $params, $fields) { $min = $params[0] ?? 0; $max = $params[1] ?? 100; return $value >= $min && $value <= $max;}, 'score', 0, 100)->message('Score must be between %d and %d');Cross-Field Validation
Section titled “Cross-Field Validation”Use $fields to access other field values:
$v->rule(function($field, $value, $params, $fields) { // Password cannot be the same as username return $value !== ($fields['username'] ?? null);}, 'password')->message('Password cannot be the same as username');Global Custom Rules
Section titled “Global Custom Rules”When you have validation logic that you’ll reuse across multiple forms or throughout your application, register it as a global rule. Global rules are available to all Validator instances and help keep your code DRY.
Registering a Global Rule
Section titled “Registering a Global Rule”Register a rule that can be used across all Validator instances:
use Frostybee\Valicomb\Validator;
// Register globallyValidator::addRule('strongPassword', function($field, $value, $params) { return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/', $value);}, 'Password must be at least 8 characters with uppercase, lowercase, and number');Using the Global Rule
Section titled “Using the Global Rule”$v = new Validator($_POST);$v->rule('strongPassword', 'password');Global Rule with Parameters
Section titled “Global Rule with Parameters”// Register rule that accepts parametersValidator::addRule('between', function($field, $value, $params) { $min = $params[0]; $max = $params[1]; return $value >= $min && $value <= $max;}, '{field} must be between %d and %d');
// Use with parameters$v->rule('between', 'age', 18, 65);Instance-Specific Rules
Section titled “Instance-Specific Rules”Instance rules sit between closures and global rules. They’re reusable within a single Validator instance but don’t pollute the global namespace. This is useful when validation logic depends on runtime context or when you want to avoid global state.
Registering an Instance Rule
Section titled “Registering an Instance Rule”Register a rule only for a specific Validator instance:
$v = new Validator($data);
$v->addInstanceRule('customCheck', function($field, $value) { return $value === 'expected';}, 'Value must be "expected"');
$v->rule('customCheck', 'field_name');// This rule is only available to this $v instanceWhen to Use Instance Rules
Section titled “When to Use Instance Rules”- Validation logic specific to one form
- Rules that depend on instance state
- Temporary or context-specific validation
Error Message Placeholders
Section titled “Error Message Placeholders”Error messages in custom rules can include dynamic placeholders that get replaced with actual values at runtime. This makes your error messages more informative and user-friendly.
Custom rule error messages support placeholders like {field}, {value}, %s, and %d. For complete documentation, see Error Messages - Message Placeholders.
Validator::addRule('divisibleBy', function($field, $value, $params) { return $value % $params[0] === 0;}, '{field} must be divisible by %d');
$v->rule('divisibleBy', 'quantity', 5);// Error: "Quantity must be divisible by 5"Real-World Examples
Section titled “Real-World Examples”These examples demonstrate common validation patterns you might encounter in production applications. Each example shows how to encapsulate complex business rules into reusable validators.
Username Validation
Section titled “Username Validation”Validator::addRule('validUsername', function($field, $value, $params) { // 3-20 chars, alphanumeric and underscores only, must start with letter return preg_match('/^[a-zA-Z][a-zA-Z0-9_]{2,19}$/', $value);}, '{field} must be 3-20 characters, start with a letter, and contain only letters, numbers, and underscores');No Profanity
Section titled “No Profanity”Validator::addRule('noProfanity', function($field, $value, $params) { $badWords = ['badword1', 'badword2']; // Your list $valueLower = strtolower($value); foreach ($badWords as $word) { if (str_contains($valueLower, $word)) { return false; } } return true;}, '{field} contains inappropriate language');Unique in Database
Section titled “Unique in Database”Validator::addRule('uniqueEmail', function($field, $value, $params, $fields) { // Example with PDO (inject your database connection) global $pdo; $stmt = $pdo->prepare('SELECT COUNT(*) FROM users WHERE email = ?'); $stmt->execute([$value]); return $stmt->fetchColumn() === 0;}, 'This email address is already registered');Matching Confirmation Field
Section titled “Matching Confirmation Field”Validator::addRule('confirmed', function($field, $value, $params, $fields) { $confirmField = $field . '_confirmation'; return isset($fields[$confirmField]) && $value === $fields[$confirmField];}, '{field} confirmation does not match');Complex Password Rules
Section titled “Complex Password Rules”Validator::addRule('securePassword', function($field, $value, $params) { $minLength = $params[0] ?? 8;
if (strlen($value) < $minLength) return false; if (!preg_match('/[a-z]/', $value)) return false; // lowercase if (!preg_match('/[A-Z]/', $value)) return false; // uppercase if (!preg_match('/[0-9]/', $value)) return false; // digit if (!preg_match('/[^a-zA-Z0-9]/', $value)) return false; // special char
return true;}, '{field} must be at least %d characters with uppercase, lowercase, number, and special character');
// Usage$v->rule('securePassword', 'password', 12);Dynamic Error Messages
Section titled “Dynamic Error Messages”Sometimes a single error message isn’t enough. When a validation rule can fail for multiple reasons, you’ll want to tell users exactly what went wrong. Dynamic error messages let your custom rules return context-specific feedback based on why the validation failed.
Instead of returning just a boolean, return an array with the result and a custom message:
Return Format
Section titled “Return Format”// Simple boolean (traditional)return true; // passedreturn false; // failed (uses default message)
// Array with custom message (new)return [true]; // passedreturn [false, 'Custom message']; // failed with custom messageExample: Password Strength
Section titled “Example: Password Strength”$v->addInstanceRule('strongPassword', function($field, $value, $params) { if (strlen($value) < 8) { return [false, 'Password must be at least 8 characters']; } if (!preg_match('/[A-Z]/', $value)) { return [false, 'Password must contain an uppercase letter']; } if (!preg_match('/[0-9]/', $value)) { return [false, 'Password must contain a number']; } return [true];});
$v->rule('strongPassword', 'password');Each failure reason produces a specific, helpful error message.
Example: Coupon Validation
Section titled “Example: Coupon Validation”Validator::addRule('validCoupon', function($field, $value, $params) { $coupons = [ 'SAVE10' => ['active' => true, 'min_order' => 50], 'EXPIRED' => ['active' => false], ];
if (!isset($coupons[$value])) { return [false, '{field} "{value}" is not a valid coupon code']; }
$coupon = $coupons[$value];
if (!$coupon['active']) { return [false, '{field} "{value}" has expired']; }
return [true];});Using Placeholders
Section titled “Using Placeholders”Dynamic messages support all standard placeholders:
$v->addInstanceRule('inStock', function($field, $value, $params) { $stock = getStockLevel($value); // hypothetical function
if ($stock === 0) { return [false, '{field} "{value}" is out of stock']; }
return [true];});