Skip to content

Validation rules

Validation rules implement ValidationRuleInterface and throw ValidationFailException when a value is rejected. A field executes its validation rules sequentially in collection order.

$form->addField(
    'email',
    new StringType(),
    [
        new ValidateNotEmpty(),
        new ValidateEmail(),
    ],
);

PHP attribute support

Small Forms 2.3.0 contains 75 validator classes. All 75 are declared as PHP attributes and support property targets, so they can be attached directly to properties inspected by AnnotationAdapter when their constructor arguments can be represented by a PHP attribute expression.

Common examples:

#[StringType]
#[ValidateNotEmpty]
#[ValidateEmail]
private string $email = '';

#[IntType]
#[ValidateRange(1, 100)]
private int $priority = 1;

#[StringType]
#[ValidateBeginWith('USR-')]
#[ValidateNumberCharsAtLeast(8)]
private string $externalId = '';

The built-in validators are:

ValidateAll, ValidateArrayItem, ValidateArrayKeys, ValidateArrayShape, ValidateAtLeastOneOf, ValidateBase64, ValidateBeginWith, ValidateBoolean, ValidateCallback, ValidateChoice, ValidateCidr, ValidateCountGreaterOrEqualThan, ValidateCountGreaterThan, ValidateCountLessOrEqualThan, ValidateCountLessThan, ValidateDateAfter, ValidateDateBefore, ValidateDateBetween, ValidateDateTime, ValidateDecimal, ValidateDifferentFromField, ValidateDivisibleBy, ValidateEach, ValidateEmail, ValidateEmpty, ValidateEndWith, ValidateEnum, ValidateEqual, ValidateFileExists, ValidateFloat, ValidateFloatArray, ValidateGreater, ValidateGreaterOrEqual, ValidateHexadecimal, ValidateHostname, ValidateInstanceOf, ValidateInt, ValidateIntArray, ValidateIp, ValidateIsFalse, ValidateIsNull, ValidateIsTrue, ValidateJson, ValidateLess, ValidateLessOrEqual, ValidateMatchRegex, ValidateMixedArray, ValidateNegativeNumber, ValidateNoneOf, ValidateNotChoice, ValidateNotEmpty, ValidateNotEqual, ValidateNotMatchRegex, ValidateNotNull, ValidateNumberCharsAtLeast, ValidateNumberCharsBetween, ValidateNumberCharsLessThan, ValidatePathIsDirectory, ValidatePathReadable, ValidatePathWritable, ValidatePosifiveNumber, ValidateRange, ValidateRequired, ValidateRequiredIf, ValidateRequiredWith, ValidateRequiredWithout, ValidateSameAsField, ValidateSequancialy, ValidateSlug, ValidateString, ValidateStringArray, ValidateUlid, ValidateUnique, ValidateUrl, ValidateUuid.

ValidateCallback is still normally created programmatically because its constructor requires a Closure, which is not a practical PHP attribute argument. Declaring a class as #[Attribute] and being convenient to instantiate in attribute syntax are separate concerns.

All validators remain available programmatically through addField() or addRule().

Presence and nullability

Useful rules include:

Rule Purpose
ValidateRequired Requires a supplied value according to the rule implementation.
ValidateNotEmpty Rejects empty values.
ValidateEmpty Requires an empty value.
ValidateNotNull Rejects null.
ValidateIsNull Requires null.
ValidateIsTrue Requires true.
ValidateIsFalse Requires false.

Many validators accept canBeNull: true by default. Combine them with an explicit presence/null rule when null must be rejected.

Cross-field validation

Five rules are form-aware. Before a form validates a field, it injects the current form and field name into these rules:

  • ValidateRequiredIf(string $otherField, mixed $expectedValue, bool $strict = true)
  • ValidateRequiredWith(string $otherField)
  • ValidateRequiredWithout(string $otherField)
  • ValidateSameAsField(string $otherField, bool $strict = true)
  • ValidateDifferentFromField(string $otherField, bool $strict = true)

Example:

#[StringType]
#[ValidateRequiredIf('type', 'company')]
private ?string $companyName = null;

Use these for input consistency. Authorization and permission checks still belong in the application/domain layer.

Strings and identifiers

Available validators include email, URL, host, IP/CIDR, UUID, ULID, slug, JSON, Base64, hexadecimal, regex and string-length rules.

Typical constructors:

new ValidateNumberCharsBetween(3, 80);
new ValidateMatchRegex('/^[A-Z0-9_-]+$/');
new ValidateEmail(canBeNull: false);
new ValidateUrl(canBeNull: true);
new ValidateUuid(canBeNull: false);

Numeric rules

new ValidateGreater(0);
new ValidateGreaterOrEqual(0);
new ValidateLess(100);
new ValidateLessOrEqual(100);
new ValidateRange(1, 10);
new ValidateDivisibleBy(5);
new ValidateDecimal(integerSize: 8, decimalSize: 2);

The public class name ValidatePosifiveNumber retains its current historical spelling.

Arrays and collections

Small Forms includes validators for array contents and structure:

new ValidateEach(new ValidateUuid());
new ValidateAll(new StringType());
new ValidateArrayItem('email', new ValidateEmail());
new ValidateArrayKeys(
    requiredKeys: ['name'],
    allowedKeys: ['name', 'email'],
);
new ValidateArrayShape([
    'name' => new ValidateNotEmpty(),
    'email' => new ValidateEmail(),
]);

Count validators can be used either as property attributes or programmatically.

Composition

new ValidateAtLeastOneOf([
    new ValidateEmail(),
    new ValidateUuid(),
]);

new ValidateNoneOf([
    new ValidateEqual('root'),
    new ValidateEqual('admin'),
]);

new ValidateSequancialy([
    new ValidateNotEmpty(),
    new ValidateEmail(),
]);

ValidateSequancialy is the current public class name, including its spelling.

Dates

new ValidateDateAfter('2026-01-01 00:00:00');
new ValidateDateBefore(new DateTimeImmutable('+1 year'));
new ValidateDateBetween(
    '2026-01-01 00:00:00',
    '2026-12-31 23:59:59',
);

All three comparison rules support a format, inclusive/exclusive boundaries and nullable values.

Custom validation

Implement ValidationRuleInterface for reusable domain-independent input checks. For a callback-based rule:

new ValidateCallback(function (array $formData, mixed $value): void {
    // Throw ValidationFailException when invalid.
});

ValidateUnique checks duplicate values inside an array/Collection; it is not a database uniqueness query.

Keep database-dependent uniqueness, authorization and race-sensitive invariants backed by authoritative database/domain checks even when a form provides an early validation layer.