Navigation: Validations | Conditional Validation | Rules & Schemas | Custom Strategies(CUSTOM_STRATEGIES.md) | Error Handling | Internationalization | Benchmarks
Extend DataVerify with your own validation logic through custom strategies.
- Quick Start
- IDE Autocompletion
- Global vs Instance Registration
- Creating Strategies
- Translation and Error Messages
- Registration Methods
- Best Practices
- API Reference
use Gravity\DataVerify;
use Gravity\Interfaces\ValidationStrategyInterface;
// 1. Create strategy
// The #[ValidationRule] attribute enables auto-documentation generation and IDE stub creation (See IDE Autocompletion)
#[ValidationRule(
name: 'siret',
description: 'Validates French SIRET number (14 digits with Luhn check)',
category: 'Business',
examples: ['$verifier->field("company")->siret']
)]
class SiretStrategy extends ValidationStrategy
{
public function getName(): string { return 'siret'; }
protected function handler(mixed $value): bool
{
return is_string($value)
&& preg_match('/^\d{14}$/', $value)
&& $this->luhnCheck($value);
}
}
// 2. Register globally
DataVerify::global()->loadFromDirectory(
path: __DIR__ . '/app/Strategies',
namespace: 'App\\Strategies'
);
// 3. Use anywhere
$dv->field('siret')->required->siret;Enable Automatic IDE Helper
// bootstrap.php (development only)
if (getenv('APP_ENV') === 'development') {
DataVerify::enableIdeHelper();
}
DataVerify::global()->register(new SiretStrategy());
// ✨ IDE now autocompletes ->siret()Add to .gitignore:
/.ide-helper.phpHow it works: Generates a stub file providing type hints for IDEs (PHPStorm, VSCode, etc.). Updates automatically when registering strategies.
Manual Generation
use Gravity\Documentation\GeneratePhpDoc;
GeneratePhpDoc::writeIdeHelper(
__DIR__ . '/.ide-helper.php',
[new SiretStrategy(), new VatStrategy()]
);Global Strategies (Recommended)
// Register once - available everywhere
DataVerify::global()->loadFromDirectory(
path: __DIR__ . '/app/Strategies',
namespace: 'App\\Strategies'
);
$dv = new DataVerify($data);
$dv->field('siret')->siret; // Works automaticallyUse for: Business rules (SIRET, VAT), domain validations, shared across app.
Instance Strategies
// Register per validation - not shared
$dv = new DataVerify($data);
$dv->registerStrategy(new TempStrategy());
$dv->field('special')->temp_validation;Use for: One-off validations, testing, controller-specific logic.
Simple Strategy (No Parameters)
#[ValidationRule(
name: 'positive',
description: 'Validates that a numeric value is strictly positive (greater than zero)',
category: 'Numeric',
examples: ['$verifier->field("amount")->positive']
)]
class PositiveStrategy extends ValidationStrategy
{
public function getName(): string { return 'positive'; }
protected function handler(mixed $value): bool
{
return is_numeric($value) && $value > 0;
}
}
// Usage
$dv->field('amount')->positive;Strategy with Parameters
Parameter names automatically become translation placeholders:
class DivisibleByStrategy extends ValidationStrategy
{
public function getName(): string { return 'divisible_by'; }
protected function handler(mixed $value, int $divisor): bool
{
return is_numeric($value) && $value % $divisor === 0;
}
}
// Translation: "The {field} must be divisible by {divisor}"
// ^^^^^^^^ from $divisor parameterStrategy with Documentation
use Gravity\Attributes\{ValidationRule, Param};
#[ValidationRule(
name: 'strong_password',
description: 'Strong password (12+ chars, mixed case, digit, special)',
category: 'Security'
)]
class StrongPasswordStrategy extends ValidationStrategy
{
public function getName(): string { return 'strong_password'; }
protected function handler(
mixed $value,
#[Param('Minimum length')]
int $minLength = 12
): bool {
return is_string($value)
&& strlen($value) >= $minLength
&& preg_match('/[A-Z]/', $value)
&& preg_match('/[a-z]/', $value)
&& preg_match('/[0-9]/', $value)
&& preg_match('/[^A-Za-z0-9]/', $value);
}
}Note: Attributes enable auto-generated documentation.
Named Parameters for Translations
Parameter names in handler() automatically become placeholders:
class PriceRangeStrategy extends ValidationStrategy
{
public function getName(): string { return 'price_range'; }
// Named parameters → {min} and {max} placeholders
protected function handler(mixed $value, float $min, float $max): bool
{
return is_numeric($value) && $value >= $min && $value <= $max;
}
}
// Add translations
$dv->addTranslations([
'validation.price_range' => 'The {field} must be between {min}€ and {max}€'
], 'en');
$dv->field('price')->price_range(10.0, 99.99);
// Error: "The field price must be between 10€ and 99.99€"Standard Parameter Names
Use conventional names to match built-in validations:
protected function handler(
mixed $value,
int $min, // → {min}
int $max, // → {max}
string $format, // → {format}
array $allowed, // → {allowed}
string $pattern // → {pattern}
): bool { }Auto-load from Directory
DataVerify::global()->loadFromDirectory(
path: __DIR__ . '/app/Strategies',
namespace: 'App\\Strategies'
);Recommended structure:
app/Strategies/
├── SiretStrategy.php
├── IbanStrategy.php
└── VatStrategy.php
Register Multiple
DataVerify::global()->registerMultiple([
new SiretStrategy(),
new IbanStrategy(),
]);Register Single
DataVerify::global()->register(new CustomStrategy());Fluent API
DataVerify::global()
->register(new Strategy1())
->registerMultiple([new Strategy2()])
->loadFromDirectory(__DIR__ . '/custom', 'App\\Custom');Always Use Named Parameters
// ✅ Good - translation support
protected function handler(mixed $value, int $min, int $max): bool
// ❌ Bad - no translation placeholders
protected function handler(mixed $value, int $a, int $b): boolImportant: Never override execute() - put logic in handler().
When to Use Each Approach
Global: Business rules (SIRET, VAT), domain validations, shared across app
Instance: One-off validations, testing, controller-specific logic
Organize Strategies
app/Strategies/
├── Business/
│ ├── SiretStrategy.php
│ └── VatStrategy.php
└── Security/
└── StrongPasswordStrategy.php
Validate Types Early
protected function handler(mixed $value, int $threshold): bool
{
if (!is_numeric($value)) return false; // Type check first
if (empty($value)) return false;
return $value >= $threshold;
}ValidationStrategy Base Class
abstract class ValidationStrategy
{
abstract public function getName(): string;
abstract protected function handler(mixed $value, mixed ...$args): bool;
final public function execute(mixed $value, array $args): bool;
}Example:
#[ValidationRule(
name: 'my_validation',
description: 'Validates a value against custom business logic',
category: 'My Domain'
)]
class MyStrategy extends ValidationStrategy
{
public function getName(): string { return 'my_validation'; }
protected function handler( mixed $value,
#[Param(name: 'param', description: 'Validation threshold parameter')]
int $param): bool
{
// $param becomes {param} in translations
}
}GlobalStrategyRegistry Methods
DataVerify::global()->register(ValidationStrategyInterface $strategy): self // Register a single strategy
DataVerify::global()->registerMultiple(array $strategies): self // Register multiple strategies at once
DataVerify::global()->loadFromDirectory(string $path, string $namespace): self // Auto-discover and load strategies from a directory
DataVerify::global()->has(string $name): bool // Check if a strategy exists
DataVerify::global()->get(string $name): ?ValidationStrategyInterface // Retrieve a specific strategy
DataVerify::global()->clear(): self // Remove all registered strategies
DataVerify::global()->getAll(): array // Get all registered strategiesDataVerify Instance Methods
$dv->registerStrategy(ValidationStrategyInterface $strategy): self- Validation Rules - All built-in validation rules
- Internationalization - Add translations for custom strategies
- Error Handling - Custom error messages for strategies
- Conditional Validation - Use strategies with conditions
Navigation: Validations | Conditional Validation | Rules & Schemas | Custom Strategies | Error Handling | Internationalization | Benchmarks