Skip to content

Latest commit

 

History

History
1042 lines (744 loc) · 30.8 KB

File metadata and controls

1042 lines (744 loc) · 30.8 KB

Migrating from v2.x to v3.x

This guide will help you migrate your code from Respect\Validation 2.x to 3.0.

Version 3.0 is a major release with breaking changes. This document is organized into two main sections: Breaking Changes (what you must update) and New Features (what you can now use).

Installation

Update your composer.json to require version 3.0:

composer require respect/validation:^3.0

Respect\Validation 3.0 requires PHP 8.5 or above (2.x required PHP 8.1+).


Breaking changes

Class and namespace changes

Main class rename

The main Validator class has been renamed to ValidatorBuilder:

- use Respect\Validation\Validator as v;
+ use Respect\Validation\ValidatorBuilder as v;

Alternatively, v is now a global class alias, so you can use it directly without any import:

- use Respect\Validation\Validator as v;

v::intType()->assert($input);

Internal terminology

Throughout the codebase, "rules" have been renamed to "validators":

2.x 3.0
Respect\Validation\Rules namespace Respect\Validation\Validators namespace
Rule interface Validator interface
AbstractRule class Base classes in Validators\Core
Factory class ValidatorFactory class
InvalidRuleConstructorException InvalidValidatorException

Validation methods

validate() now returns an object

In 2.x, validate() returned a boolean. In 3.0, it returns a ResultQuery object:

- if (v::intType()->validate($input)) {
+ if (v::intType()->isValid($input)) {
      // validation passed
  }

check() removed, assert() unified

In 2.x, there were two exception-based methods:

  • check() threw rule-specific exceptions (e.g., IntTypeException)
  • assert() threw NestedValidationException

In 3.0, both are unified into assert(), which throws ValidationException:

-use Respect\Validation\Exceptions\IntTypeException;
+use Respect\Validation\Exceptions\ValidationException;

try {
-	v::intType()->check($input);
-} catch (IntTypeException $exception) {
+	v::intType()->assert($input);
+} catch (ValidationException $exception) {
    echo $exception->getMessage();
}

The ValidationException provides all methods previously split between exceptions:

-use Respect\Validation\Exceptions\NestedValidationException;
+use Respect\Validation\Exceptions\ValidationException;

try {
    v::intType()->positive()->assert($input);
-} catch (NestedValidationException $exception) {
+} catch (ValidationException $exception) {
    $exception->getMessage();       // First error message
    $exception->getFullMessage();   // Full error tree
    $exception->getMessages();      // All errors as array
}

Validator changes

Removed validators

Type

The type() validator has been replaced in favor of specific type validators:

- v::type('string')->assert($input);
+ v::stringType()->assert($input);

- v::type('int')->assert($input);
+ v::intType()->assert($input);

- v::type('array')->assert($input);
+ v::arrayType()->assert($input);
Yes and No
- v::yes()->assert('yes');
+ v::trueVal()->assert('yes');

- v::no()->assert('no');
+ v::falseVal()->assert('no');
KeyNested
- v::keyNested('user.profile.name', v::stringType())->assert($data);
+ v::key('user', v::key('profile', v::key('name', v::stringType())))->assert($data);
Age, MinAge, and MaxAge

These validators have been replaced by DateTimeDiff, which provides more flexibility by allowing you to specify the time unit and comparison validator:

- v::minAge(18)->assert($birthDate);
+ v::dateTimeDiff('years', v::greaterThanOrEqual(18))->assert($birthDate);

- v::maxAge(65)->assert($birthDate);
+ v::dateTimeDiff('years', v::lessThanOrEqual(65))->assert($birthDate);

- v::age(18, 65)->assert($birthDate);
+ v::dateTimeDiff('years', v::between(18, 65))->assert($birthDate);
PrimeNumber, Fibonacci, PerfectSquare

Combine Satisfies with a mathematical library of your choice:

- v::primeNumber()->assert(7);
+ v::satisfies(static fn ($input) => \MathPHP\NumberTheory\Integer::isPrime($input))->assert(7);

See: https://github.com/markrogoyski/math-php

FilterVar

Use Satisfies instead:

- v::filterVar(FILTER_VALIDATE_INT)->assert(123);
+ v::satisfies(static fn($input) => filter_var($input, FILTER_VALIDATE_INT) !== false)->assert(123);
Uploaded

Use Satisfies instead:

- v::uploaded()->assert($fileName);
+ v::satisfies('is_uploaded_file')->assert($fileName);
VideoUrl

We offer no recommendation for replacing this validator.

Behavior changes

Attribute replaced by Property variants

The Attribute validator has been replaced by three separate validators with different signatures.

Validator Description
Property Property must exist and pass validation
PropertyOptional Validates only if property exists
PropertyExists Only checks if property exists
  // Property must exist and be valid
- v::attribute('name', v::stringType())->assert($user);
+ v::property('name', v::stringType())->assert($user);

  // Property is optional (mandatory = false)
- v::attribute('age', v::intType(), false)->assert($user);
+ v::propertyOptional('age', v::intType())->assert($user);

  // Only check existence (no validator, mandatory = true)
- v::attribute('id')->assert($user);
+ v::propertyExists('id')->assert($user);
Key split into three validators

The Key validator has been split into three separate validators with different signatures.

Validator Description
Key Key must exist and pass validation
KeyOptional Validates only if key exists
KeyExists Only checks if key exists
  // Key must exist and be valid
- v::key('email', v::email())->assert($data);
+ v::key('email', v::email())->assert($data);

  // Key is optional (mandatory = false)
- v::key('phone', v::phone(), false)->assert($data);
+ v::keyOptional('phone', v::phone())->assert($data);

  // Only check existence (no validator, mandatory = true)
- v::key('id')->assert($data);
+ v::keyExists('id')->assert($data);
Length signature changed

The length() validator no longer accepts scalar min/max arguments. Use the new composition syntax:

- v::length(5, 10)->assert($input);
+ v::length(v::between(5, 10))->assert($input);

- v::length(5, null)->assert($input);
+ v::length(v::greaterThanOrEqual(5))->assert($input);

- v::length(null, 10)->assert($input);
+ v::length(v::lessThanOrEqual(10))->assert($input);

Or use the prefixed shortcuts:

v::lengthBetween(5, 10)->assert($input);
v::lengthGreaterThanOrEqual(5)->assert($input);
v::lengthLessThanOrEqual(10)->assert($input);
Size signature changed

The size() validator no longer accepts string sizes like '5MB'. Use the new composition syntax:

- v::size('5MB', '10MB')->assert($file);
+ v::size('MB', v::between(5, 10))->assert($file);

- v::size('5MB', null)->assert($file);
+ v::size('MB', v::greaterThanOrEqual(5))->assert($file);

- v::size(null, '10MB')->assert($file);
+ v::size('MB', v::lessThanOrEqual(10))->assert($file);
Each validator stricter

The Each validator now rejects stdClass and other non-iterable values:

// These now fail in 3.0
v::each(v::alwaysValid())->isValid(new stdClass()); // false
Composite validators require two or more validators

AllOf, AnyOf, NoneOf, and OneOf now require at least two validators:

- v::allOf(v::intType())->assert($input);
+ v::intType()->assert($input);
After does not handle errors anymore

After (previously Call) now does not handle PHP errors inside the callback you provided.

v::after('strtolower', v::equals('foo'))->assert(123); // Error bubbles out

You can use anonymous functions to handle errors or perform type conversions instead:

v::after(static fn ($i) => strtolower((string) $i), v::equals('123'));
Contains, ContainsAny, In, EndsWith and StartsWith strict by default.

The parameter $identical, which controlled case-insensitivy and strict typing was removed. Now these validators will always compare case-sensitive and use strict typing.

- v::contains('needle', identical: true);
- v::containsAny(['needle1', 'needle2'], identical: true);
- v::in(['hay', 'stack'], compareIdentical: true);
- v::startsWith('needle', identical: true);
- v::endsWith('needle', identical: true);
+ v::contains('needle');                     // always strict case and type
+ v::containsAny(['needle1', 'needle2']);    // always strict case and type
+ v::in(['hay', 'stack']);                   // always strict case and type
+ v::startsWith('needle');                   // always strict case and type
+ v::endsWith('needle');                     // always strict case and type

For more information, refer to case-sensitiveness.md

Url now validates domains, IP addresses and domains when appropriate.
  • news scheme is now unsupported. Consult the validator documentation for the list of supported schemes.
  • mailto URLs now are also validated with the Email validator.
  • Supported schemes now validate using Ip and Domain when appropriate.
New package dependencies

Some validators now require additional packages:

Validators Required packages
CountryCode, LanguageCode, SubdivisionCode, CurrencyCode sokil/php-isocodes and sokil/php-isocodes-db-only
Uuid ramsey/uuid

Install with:

composer require sokil/php-isocodes sokil/php-isocodes-db-only
composer require ramsey/uuid

Renamed validators

2.x 3.0 Notes
Call After Clearer intent
Callback Satisfies Clearer intent
Min GreaterThanOrEqual Clearer comparison semantics
Max LessThanOrEqual Clearer comparison semantics
Nullable NullOr Consistent naming pattern
Optional UndefOr Validates if value is undefined or passes
KeyValue Factory Deferred validator creation
- v::call('strtolower', v::equals('foo'))->assert($input);
+ v::after('strtolower', v::equals('foo'))->assert($input);

- v::callback('is_int')->assert($input);
+ v::satisfies('is_int')->assert($input);

- v::min(18)->assert($age);
+ v::greaterThanOrEqual(18)->assert($age);

- v::max(100)->assert($age);
+ v::lessThanOrEqual(100)->assert($age);

- v::nullable(v::email())->assert($email);
+ v::nullOr(v::email())->assert($email);

- v::optional(v::email())->assert($email);
+ v::undefOr(v::email())->assert($email);

In 3.0, Min and Max validators exist but have different semantics. They extract the minimum/maximum value from a collection and validate it (see Result composition).

Validator 2.x 3.x
Min Single value >= Pick minimum value from iterable and validate
Max Single value <= Pick minimum value from iterable and validate
NotBlank logic inverted

In 2.x, NotBlank validated that a value was not blank. In 3.0, Blank validates that a value is blank, so you need to negate it:

- v::notBlank()->assert($input);
+ v::not(v::blank())->assert($input);

Or use the prefixed shortcut (see New Features):

v::notBlank()->assert($input);
NotEmpty renamed to Falsy

The NotEmpty validator was renamed to Falsy with inverted logic:

- v::notEmpty()->assert($input);
+ v::not(v::falsy())->assert($input);

Or use the prefixed shortcut:

v::notFalsy()->assert($input);
NoWhitespace renamed to Spaced

The NoWhitespace validator was renamed to Spaced with inverted logic. Spaced validates that a string contains whitespace:

- v::noWhitespace()->assert($input);
+ v::not(v::spaced())->assert($input);

Or use the prefixed shortcut:

v::notSpaced()->assert($input);
IterableType renamed to IterableVal
- v::iterableType()->assert($input);
+ v::iterableVal()->assert($input);

Note: IterableType still exists in 3.0 but now strictly uses PHP's is_iterable() function (rejecting stdClass).

Custom message methods removed

setTemplate() removed

- v::intType()->setTemplate('Please provide an integer')->check($input);
+ v::templated('Please provide an integer', v::intType())->assert($input);

setName() removed

- v::intType()->setName('User ID')->check($input);
+ v::named('User ID', v::intType())->assert($input);

{{name}} placeholder renamed

The main placeholder in message templates has been renamed from {{name}} to {{subject}}:

- {{name}} must be valid
+ {{subject}} must be valid

The value of {{subject}} is determined by context:

Context Value Example
Key/Property/Each validators The path `.email` must be valid
Named validator The custom name Email must be valid
Both path and name defined Path with name suffix `.email` (<- User Email) must be valid
No path or name The input value "invalid#email" must be valid

Factory and container changes

The Factory class has been replaced by a dependency injection container approach using ContainerRegistry:

- use Respect\Validation\Factory;
+ use Respect\Validation\ContainerRegistry;
+ use Symfony\Contracts\Translation\TranslatorInterface;

- Factory::setDefaultInstance(
-     (new Factory())
-         ->withRuleNamespace('My\\Validation\\Rules')
-         ->withTranslator($translator)
- );
+ $container = ContainerRegistry::createContainer([
+     // Add custom validator namespaces
+     'respect.validation.rule_factory.namespaces' => [
+         'My\\Validation\\Validators',
+         'Respect\\Validation\\Validators', // Keep the default namespace
+     ],
+     // Add a translator
+     TranslatorInterface::class => $translator,
+ ]);
+ ContainerRegistry::setContainer($container);

The ContainerRegistry::createContainer() returns a PHP-DI container. You can also use any PSR-11 compatible container with ContainerRegistry::setContainer().

Custom validators

In 2.x, custom validators required a separate exception class to define error message templates. In 3.0, templates are defined using PHP attributes directly on the validator class.

Before (2.x): Two files required - validator and exception:

// CustomException.php
use Respect\Validation\Exceptions\ValidationException;

final class CustomException extends ValidationException
{
    protected $defaultTemplates = [
        self::MODE_DEFAULT => [
            self::STANDARD => '{{name}} must be custom',
        ],
        self::MODE_NEGATIVE => [
            self::STANDARD => '{{name}} must not be custom',
        ],
    ];
}

// Custom.php
use Respect\Validation\Rules\AbstractRule;

final class Custom extends AbstractRule
{
    public function validate($input): bool
    {
        return $this->checkSomething($input);
    }

    // Implementation of checkSomething()...
}

After (3.0): Single file with #[Template] attribute:

use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;

#[Template(
    '{{subject}} must be custom',
    '{{subject}} must not be custom',
)]
final class Custom implements Validator
{
    public function evaluate(mixed $input): Result
    {
        return Result::of($this->checkSomething($input), $input, $this);
    }

    // Implementation of checkSomething()...
}

Base classes available in Respect\Validation\Validators\Core:

  • Simple - For validators with simple boolean logic
  • Composite - For validators that combine multiple validators
  • Envelope - For validators that modify how another validator works

New features

New validators

Version 3.0 introduces several new validators:

Validator Description
All Validates that every item in an iterable passes validation
Attributes Validates object properties using PHP attributes
BetweenExclusive Validates that a value is between two bounds (exclusive)
Circuit Short-circuit validation, stops at first failure
ContainsCount Validates the count of occurrences in a value
DateTimeDiff Validates date/time differences (replaces Age validators)
Hetu Validates Finnish personal identity codes (henkilötunnus)
KeyExists Checks if an array key exists
KeyOptional Validates an array key only if it exists
Factory Creates validators dynamically based on input
Masked Masks sensitive input values in error messages
Named Customizes the subject name in error messages
PropertyExists Checks if an object property exists
PropertyOptional Validates an object property only if it exists
Templated Attaches custom error message templates

All

Validates that every item in an iterable passes the given validator:

v::all(v::intType())->assert([1, 2, 3]); // passes
v::all(v::positive())->assert([1, -2, 3]); // fails

Attributes

Validates object properties using PHP attributes defined on the class:

use Respect\Validation\Validators as Validator;

final readonly class User
{
    public function __construct(
        #[Validator\Email]
        public string $email,

        #[Validator\Between(18, 120)]
        public int $age,

        #[Validator\Length(new Validator\Between(1, 100))]
        public string $name,
    ) {
    }
}

// Validate all properties at once
v::attributes()->assert($user);

BetweenExclusive

Validates that a value is between two bounds, excluding the bounds themselves:

v::betweenExclusive(1, 10)->assert(5); // passes
v::betweenExclusive(1, 10)->assert(1); // fails (1 is not > 1)
v::betweenExclusive(1, 10)->assert(10); // fails (10 is not < 10)

Circuit

Validates input against a series of validators, stopping at the first failure. Useful for dependent validations:

$validator = v::circuit(
    v::key('countryCode', v::countryCode()),
    v::factory(
        fn($input) => v::key(
            'subdivisionCode',
            v::subdivisionCode($input['countryCode'])
        )
    ),
);

$validator->assert([]); // → `.countryCode` must be present
$validator->assert(['countryCode' => 'US']); // → `.subdivisionCode` must be present
$validator->assert(['countryCode' => 'US', 'subdivisionCode' => 'CA']); // passes

ContainsCount

Validates the count of occurrences of a value:

v::containsCount('a', v::equals(3))->assert('banana'); // passes (3 'a's)
v::containsCount('x', v::greaterThan(0))->assert('example'); // passes

DateTimeDiff

Validates date/time differences. Replaces the removed Age, MinAge, and MaxAge validators:

v::dateTimeDiff('years', v::greaterThanOrEqual(18))->assert('2000-01-01'); // passes if 18+ years ago
v::dateTimeDiff('days', v::lessThan(30))->assert('2024-01-15'); // passes if less than 30 days ago

Hetu

Validates Finnish personal identity codes (henkilötunnus):

v::hetu()->assert('010101-123N'); // passes

KeyExists

Checks if an array key exists (without validating its value):

v::keyExists('email')->assert(['email' => 'user@example.com']); // passes
v::keyExists('email')->assert(['name' => 'John']); // fails

KeyOptional

Validates an array key only if it exists:

v::keyOptional('phone', v::phone())->assert(['name' => 'John']); // passes (key absent)
v::keyOptional('phone', v::phone())->assert(['phone' => '+1234567890']); // passes
v::keyOptional('phone', v::phone())->assert(['phone' => 'invalid']); // fails

Factory

Creates validators dynamically based on the input, useful for cross-field validation:

// Validate that 'confirmation' matches 'password'
v::factory(
    fn($input) => v::key('confirmation', v::equals($input['password'] ?? null))
)->assert(['password' => 'secret', 'confirmation' => 'secret']); // passes

Masked

Decorates a validator to mask sensitive input values in error messages while still validating the original unmasked data. This allows applications to protect sensitive information like passwords, credit cards, or emails without implementing a custom layer between Validation and end users:

v::masked('1-@', v::email(),v::email())->assert('invalid@example.com');
// → "*******@example.com" must be a valid email address

v::masked('6-12', v::creditCard(), 'X')->assert('4111111111111211');
// → "41111XXXXXXX1211" must be a valid credit card number

Named

Customizes the subject name in error messages:

v::named('User ID', v::intType())->assert('abc');
// → User ID must be an integer

PropertyExists

Checks if an object property exists (without validating its value):

v::propertyExists('name')->assert($user); // passes if $user->name exists

PropertyOptional

Validates an object property only if it exists:

v::propertyOptional('nickname', v::stringType())->assert($user); // passes if absent
v::propertyOptional('nickname', v::stringType())->assert($user); // validates if present

Templated

Attaches custom error message templates to validators:

v::templated('Please provide a valid number', v::intType())->assert('abc');
// → Please provide a valid number

Result-based validation

The validate() method now returns a ResultQuery object that provides detailed error inspection:

$result = v::numericVal()->positive()->between(1, 255)->validate($input);

$result->hasFailed();      // Check if the validation has failed (boolean)
$result->getMessage();     // Get the first error message
$result->getFullMessage(); // Get all error messages as a tree
$result->getMessages();    // Get all error messages as an array

Finding by path

Use findByPath() to locate validation results using dot notation for nested structures:

$result = v::init()
    ->key('user', v::key('email', v::email()))
    ->key('items', v::each(v::positive()))
    ->validate([
        'user' => ['email' => 'invalid'],
        'items' => [10, -5, 20],
    ]);

echo $result->findByPath('user.email');
// → `.user.email` must be a valid email address

echo $result->findByPath('items.1');
// → `.items.1` must be a positive number

Finding by name

Use findByName() to locate validation results by the custom name set with the Named validator:

$result = v::named('User Email', v::email())->validate('bad');

echo $result->findByName('User Email');
// → User Email must be a valid email address

Finding by ID

Use findById() to locate validation results by the validator's ID (e.g., stringType, email):

$result = v::stringType()->email()->validate(123);

echo $result->findById('stringType');
// → 123 must be a string

All finder methods return either a ResultQuery instance or null if not found.

Prefixed shortcuts

Version 3.0 introduces convenient prefixed shortcuts for common patterns:

// Null-or validators
v::nullOrEmail()->assert(null);              // passes
v::nullOrEmail()->assert('user@example.com'); // passes

// Undefined-or validators
v::undefOrPositive()->assert(null);  // passes
v::undefOrPositive()->assert(5);     // passes

// All (array item) validators
v::allPositive()->assert([1, 2, 3]);        // passes
v::allEmail()->assert(['a@b.com', 'c@d.com']); // passes

// Key validators
v::keyEmail('email')->assert(['email' => 'user@example.com']); // passes

// Property validators
v::propertyPositive('age')->assert($user); // passes if $user->age is positive

// Length validators
v::lengthBetween(5, 10)->assert('hello'); // passes

// Min/Max validators
v::minGreaterThan(0)->assert([1, 2, 3]);  // passes
v::maxLessThan(10)->assert([1, 2, 3]);    // passes

// Negation prefix
v::notBlank()->assert('hello'); // passes

Result composition

Validators compose results for clearer error messages:

v::all(v::intType())->assert(['1', '2', '3']);
// → Every item in ["1", "2", "3"] must be an integer

v::length(v::greaterThan(3))->assert('abc');
// → The length of "abc" must be greater than 3

v::key('age', v::greaterThanOrEqual(18))->assert(['age' => 16]);
// → `.age` must be greater than or equal to 18

Paths in error messages

Error messages now include full paths for nested structures:

v::init()
    ->key('mysql', v::init()->key('host', v::stringType()))
    ->key('postgresql', v::init()->key('host', v::stringType()))
    ->assert($input);
// → `.mysql.host` must be a string

Helpful stack traces

When validation fails, stack traces now point to your code instead of library internals.

2.x:

Stack trace:
#0 /opt/vendor/respect/validation/library/Factory.php(233): ReflectionClass->newInstance(1.0, 'intVal', Array, Object(Respect\Validation\Message\Formatter))
#1 /opt/vendor/respect/validation/library/Factory.php(164): Respect\Validation\Factory->createValidationException('Respect\\Validat...', 'intVal', 1.0, Array, Object(Respect\Validation\Message\Formatter))
#2 /opt/vendor/respect/validation/library/Rules/AbstractRule.php(70): Respect\Validation\Factory->exception(Object(Respect\Validation\Rules\IntVal), 1.0, Array)
#3 /opt/vendor/respect/validation/library/Rules/AbstractRule.php(45): Respect\Validation\Rules\AbstractRule->reportError(1.0)
#4 /opt/vendor/respect/validation/library/Rules/AbstractRule.php(53): Respect\Validation\Rules\AbstractRule->assert(1.0)
#5 /opt/vendor/respect/validation/library/Rules/AllOf.php(50): Respect\Validation\Rules\AbstractRule->check(1.0)
#6 /opt/vendor/respect/validation/library/Validator.php(68): Respect\Validation\Rules\AllOf->check(1.0)
#7 /opt/example.php(11): Respect\Validation\Validator->check(1.0)
#8 {main}

3.0:

Stack trace:
#0 /opt/example.php(11): Respect\Validation\Validator->assert(1.0)
#1 {main}

Custom exceptions in assert()

Pass your own exception directly to assert():

v::email()->assert($input, new DomainException('Invalid email'));

Or use a callable to access the original exception message:

v::email()->assert(
    $input,
    fn(ValidationException $e) => new DomainException($e->getMessage()),
);

Placeholder pipes

Customize how values are rendered in templates using pipes:

// `|raw` - removes quotes
v::templated(
    '{{subject|raw}} is not a UUID v{{version}}',
    v::uuid(1),
)->assert('eb3115e5-bd16-4939-ab12-2b95745a30f3');
// → eb3115e5-bd16-4939-ab12-2b95745a30f3 is not a UUID v1

// `|quote` - uses backticks
v::templated(
    'Value must match {{pattern|quote}}',
    v::regex('/^\d+$/'),
)->assert('abc');
// → Value must match `/^\d+$/`

// `|trans` - translates the value (when using a translator)
v::templated(
    'Le champ {{field|trans}} est invalide',
    v::email(),
    ['field' => 'email_address'],
)->assert('invalid');
// → Le champ "adresse e-mail" est invalide

// `|list:or` and `|list:and` - formats arrays as readable lists
v::templated(
    'Status must be {{haystack|list:or}}',
    v::in(['active', 'pending']),
)->assert('deleted');
// → Status must be "active" or "pending"

Quick reference

  // Main class
- use Respect\Validation\Validator as v;
+ use Respect\Validation\ValidatorBuilder as v;

  // Boolean validation (unchanged)
  v::intType()->isValid($input);

  // Result-based validation
- v::intType()->validate($input);              // bool
+ v::intType()->isValid($input);               // bool

  // Exception-based validation
- v::intType()->check($input);   // IntTypeException
+ v::intType()->assert($input);  // ValidationException

- v::intType()->assert($input);  // NestedValidationException
+ v::intType()->assert($input);  // ValidationException

  // Renamed validators
- v::min(18);
+ v::greaterThanOrEqual(18);

- v::max(100);
+ v::lessThanOrEqual(100);

- v::nullable(v::email());
+ v::nullOr(v::email());

  // Attribute → Property
- v::attribute('name', v::stringType());
+ v::property('name', v::stringType());

- v::attribute('age', v::intType(), false);
+ v::propertyOptional('age', v::intType());

- v::attribute('id');
+ v::propertyExists('id');

  // Key changes
- v::key('phone', v::phone(), false);
+ v::keyOptional('phone', v::phone());

- v::key('id');
+ v::keyExists('id');

  // Removed validators
- v::keyNested('a.b.c', $validator);
+ v::key('a', v::key('b', v::key('c', $validator)));

- v::yes();
+ v::trueVal();

- v::no();
+ v::falseVal();

- v::minAge(18);
+ v::dateTimeDiff('years', v::greaterThanOrEqual(18));

- v::maxAge(65);
+ v::dateTimeDiff('years', v::lessThanOrEqual(65));

Getting help