Validation Service Provider
Form validation runtime, rule registry and localized validation messages.
Responsibility
Registers RuleRegistry, alias validation.rules, ValidationRuleResolver, FormValidation and alias validator. FormValidation depends on TranslatorInterface, so validation messages can be localized through the localization provider.
Validation model
FormValidation builds a ValidationSchema through fluent field builders. Each field has a name, optional label and ordered rule definitions. validate() executes required rules first, skips optional empty values for most non-required rules, resets the current schema and returns ValidationResult.
use Lemonade\Framework\Validation\FormValidation;
$validator = $container->get('validator');
$result = $validator
->field('email', 'E-mail')->required()->email()
->field('message', 'Message')->required()->minLength(10)
->validate([
'email' => 'user@example.test',
'message' => 'Hello world',
]);
Validation result
ValidationResult exposes isValid(), errors(), error($field), validated(), failedRules() and toArray(). The validated payload contains schema fields and their submitted values; failed input is available in toArray() when validation fails.
if (!$result->isValid()) {
$errors = $result->errors();
$emailError = $result->error('email');
}
$validated = $result->validated();
Built-in rules
Built-in rule names are defined by ValidationRuleName and registered in RuleRegistry. They cover required/conditional rules (required, required_if, required_with, required_without, isset), string and format rules (valid_email, valid_emails, min_length, max_length, exact_length, alpha, alpha_numeric, regex_match, in_list), numeric and comparison rules (numeric, integer, decimal, greater_than, less_than), URL/IP/UUID rules, date/time rules, phone/address rules, database uniqueness rules, password, reCAPTCHA and no_html.
Fluent builder
ValidationFieldBuilder exposes typed methods for built-in rules, such as required(), email(), minLength(), maxLength(), integer(), inList(), matches(), requiredIf(), skipIf() and noHtml(). For custom or advanced rules, use custom($name, $param, $message).
$result = $validator
->field('password', 'Password')->required()->password()
->field('confirm_password', 'Confirm password')->required()->matches('password')
->field('age', 'Age')->integer()->greaterThanOrEqualTo(18)
->validate($payload);
Controller usage
Framework controllers expose validator() through AbstractController. Use it in form actions to validate parsed request data, then return errors or continue with validated values according to the application response flow.
final class ContactController
{
public function submit(): ResponseInterface
{
$payload = $this->request()->getParsedBody();
$result = $this->validator()
->field('email', 'E-mail')->required()->email()
->field('message', 'Message')->required()->minLength(10)
->validate(is_array($payload) ? $payload : []);
if (!$result->isValid()) {
// Render form response with $result->errors().
}
// Continue with $result->validated().
}
}
Error messages
Messages are resolved in this order: field-specific rule message, rule failure details, translated validation.{rule} line and finally built-in English fallback. Placeholders use {field} for the field label and {param} for the rule parameter.
$result = $validator
->field('email', 'E-mail')
->required('Please enter {field}.')
->email('{field} must be a valid address.')
->validate($payload);
Localization relation
FormValidation uses TranslatorInterface for validation.* messages. setLocale() can override the translator locale for one validator instance; AbstractController::setLang() sets locale on both translator and validator.
$validator->setLocale('en');
$result = $validator
->field('email', 'E-mail')->required()->email()
->validate($payload);
Custom rules
Custom rules implement ValidationRuleInterface and can be registered through RuleRegistry::addRule(). Class-string rules are resolved through the container; rule object instances can be registered directly.
use Lemonade\Framework\Validation\Rule\RuleRegistry;
use Lemonade\Framework\Validation\Rule\ValidationRuleInterface;
final class UppercaseRule implements ValidationRuleInterface
{
public function validate(mixed $value, ?string $param, array $data): bool
{
return is_string($value) && strtoupper($value) === $value;
}
}
$container
->get(RuleRegistry::class)
->addRule('uppercase', UppercaseRule::class);
$result = $validator
->field('code', 'Code')->required()->custom('uppercase')
->validate($payload);
Rule registry
RuleRegistry maps rule names to rule classes or rule instances. ValidationRuleResolver resolves registered rules through the container and caches resolved rule objects for repeated validation calls.
Upload relation
File and image upload validation is handled by UploadServiceProvider through upload profiles, file validators and image validators. ValidationServiceProvider owns form/value validation rules and does not replace upload-specific validation.