Skip to content

Validation

Learn how to validate Dtos using automatic rule inferring and validation attributes.

Validation ensures that data meets specific requirements before being processed. SimpleDto provides:

  • Automatic rule inferring from types and attributes
  • 30+ validation attributes
  • Framework integration (Laravel, Symfony)
  • Custom validation rules
  • Validation caching (198x faster)
use event4u\DataHelpers\SimpleDto;
use event4u\DataHelpers\SimpleDto\Attributes\Required;
use event4u\DataHelpers\SimpleDto\Attributes\Email;
use event4u\DataHelpers\SimpleDto\Attributes\Between;
use event4u\DataHelpers\SimpleDto\Enums\SerializationFormat;
class UserDto extends SimpleDto
{
public function __construct(
#[Required]
public readonly string $name,
#[Required, Email]
public readonly string $email,
#[Required, Between(18, 120)]
public readonly int $age,
) {}
}
// Validate and create from array (default)
$dto = UserDto::validateAndCreate([
'name' => 'John Doe',
'email' => 'john@example.com',
'age' => 30,
]);
// Validate and create from JSON
$dto = UserDto::validateAndCreate($jsonString, SerializationFormat::Json);
// Validate and create from XML
$dto = UserDto::validateAndCreate($xmlString, SerializationFormat::Xml);
use event4u\DataHelpers\SimpleDto\Exceptions\ValidationException;
try {
$dto = UserDto::validateAndCreate([
'name' => '', // ❌ Required
'email' => 'invalid', // ❌ Invalid email
'age' => 15, // ❌ Too young
]);
} catch (ValidationException $e) {
echo $e->getMessage();
print_r($e->errors());
}

For maximum performance, create DTOs without validation and validate later when needed:

// Create DTO without validation (ultra-fast)
$dto = UserDto::fromArray([
'name' => 'John Doe',
'email' => 'invalid-email',
'age' => 15,
]);
// Validate later (throws ValidationException on error)
$dto->validateInstance();
// Or validate without throwing exception
$isValid = $dto->validateInstance(false);
if (!$isValid) {
$errors = $dto->getValidationErrors();
// Check if field has errors
if ($errors->has('email')) {
echo $errors->first('email'); // Get first error message
}
// Get all errors for a field
$emailErrors = $errors->get('email');
// Get all fields with errors
$fields = $errors->fields(); // ['email', 'age']
// Get all error messages
$messages = $errors->messages();
// Count errors
$errorCount = $errors->count(); // Number of fields with errors
$messageCount = $errors->countMessages(); // Total number of messages
}
// Check validation state
$dto->isValidated(); // true/false - has validation been run?
$dto->isValid(); // true/false/null - is DTO valid? (null if not validated yet)

Benefits of On-Demand Validation:

  • Ultra-fast DTO creation (~1.5μs with UltraFast mode)
  • Validate only when needed (e.g., before saving to database)
  • Flexible error handling (throw exception or return bool)
  • Rich error API (ValidationErrorCollection with convenient methods)
use event4u\DataHelpers\SimpleDto\Attributes\Required;
class UserDto extends SimpleDto
{
public function __construct(
#[Required]
public readonly string $name,
#[Required]
public readonly string $email,
// Optional - no Required attribute
public readonly ?string $phone = null,
) {}
}
use event4u\DataHelpers\SimpleDto\Attributes\StringType;
use event4u\DataHelpers\SimpleDto\Attributes\Min;
use event4u\DataHelpers\SimpleDto\Attributes\Max;
use event4u\DataHelpers\SimpleDto\Attributes\Between;
class PostDto extends SimpleDto
{
public function __construct(
#[Required, StringType, Min(3)]
public readonly string $title,
#[Required, StringType, Between(10, 1000)]
public readonly string $content,
#[StringType, Max(100)]
public readonly ?string $excerpt = null,
) {}
}
use event4u\DataHelpers\SimpleDto\Attributes\IntegerType;
use event4u\DataHelpers\SimpleDto\Attributes\Numeric;
use event4u\DataHelpers\SimpleDto\Attributes\Min;
use event4u\DataHelpers\SimpleDto\Attributes\Max;
class ProductDto extends SimpleDto
{
public function __construct(
#[Required, IntegerType, Min(1)]
public readonly int $quantity,
#[Required, Numeric, Min(0)]
public readonly float $price,
#[IntegerType, Between(0, 100)]
public readonly ?int $discount = null,
) {}
}
use event4u\DataHelpers\SimpleDto\Attributes\Email;
class ContactDto extends SimpleDto
{
public function __construct(
#[Required, Email]
public readonly string $email,
#[Email]
public readonly ?string $alternativeEmail = null,
) {}
}
use event4u\DataHelpers\SimpleDto\Attributes\Url;
class WebsiteDto extends SimpleDto
{
public function __construct(
#[Required, Url]
public readonly string $website,
#[Url]
public readonly ?string $blog = null,
) {}
}

Validate length (maximum or range) for strings, numbers, and arrays. Perfect for database column types like varchar(10) or int(3).

Syntax:

  • One parameter: Maximum length (0 to $max)
  • Two parameters: Length range ($min to $max)
use event4u\DataHelpers\SimpleDto\Attributes\Length;
class ProductDto extends SimpleDto
{
public function __construct(
// Maximum length (0 to max)
#[Required, Length(10)]
public readonly string $name, // varchar(10) - 0-10 characters
#[Required, Length(3)]
public readonly int $countryCode, // int(3) - 0-3 digits
// Length range (min to max)
#[Required, Length(3, 10)]
public readonly string $username, // 3-10 characters
#[Required, Length(1, 3)]
public readonly int $code, // 1-3 digits
// Array length
#[Length(5)]
public readonly ?array $tags = null, // 0-5 items
) {}
}
// Valid examples
$dto = ProductDto::validateAndCreate([
'name' => 'Product', // ✅ 7 characters (0-10)
'countryCode' => 123, // ✅ 3 digits (0-3)
'username' => 'john', // ✅ 4 characters (3-10)
'code' => 12, // ✅ 2 digits (1-3)
'tags' => ['a', 'b'], // ✅ 2 items (0-5)
]);
// Invalid examples
$dto = ProductDto::validateAndCreate([
'name' => 'Very Long Product Name', // ❌ Too long (>10 characters)
'countryCode' => 1234, // ❌ Too many digits (>3)
'username' => 'ab', // ❌ Too short (<3 characters)
'code' => 1234, // ❌ Too many digits (>3)
'tags' => ['a', 'b', 'c', 'd', 'e', 'f'], // ❌ Too many items (>5)
]);

Validation Rules:

  • Strings: Character length (using mb_strlen)
  • Numbers: Number of digits (e.g., 123 = 3 digits, -999 = 3 digits)
  • Arrays: Number of items
  • Null values: Always pass (use #[Required] for mandatory fields)

Custom Error Messages:

#[Length(10, message: 'Name must be at most 10 characters')]
public readonly string $name;
#[Length(3, 10, message: 'Username must be between 3 and 10 characters')]
public readonly string $username;
AttributeDescriptionExample
RequiredField is required#[Required]
EmailValid email address#[Email]
UrlValid URL#[Url]
LengthMaximum or range length#[Length(10)] or #[Length(3, 10)]
MinMinimum value/length#[Min(3)]
MaxMaximum value/length#[Max(100)]
BetweenValue between min and max#[Between(18, 120)]
StringTypeMust be string#[StringType]
IntegerTypeMust be integer#[IntegerType]
NumericMust be numeric#[Numeric]
BooleanTypeMust be boolean#[BooleanType]
ArrayTypeMust be array#[ArrayType]
InValue in list#[In(['active', 'inactive'])]
NotInValue not in list#[NotIn(['banned'])]
RegexMatches regex#[Regex('/^[A-Z]/')]
AlphaOnly letters#[Alpha]
AlphaNumLetters and numbers#[AlphaNum]
AlphaDashLetters, numbers, dashes#[AlphaDash]
UuidValid UUID#[Uuid]
JsonValid JSON#[Json]
DateValid date#[Date]
DateFormatDate in format#[DateFormat('Y-m-d')]
BeforeDate before#[Before('2024-12-31')]
AfterDate after#[After('2024-01-01')]
IpValid IP address#[Ip]
Ipv4Valid IPv4#[Ipv4]
Ipv6Valid IPv6#[Ipv6]
MacAddressValid MAC address#[MacAddress]
TimezoneValid timezone#[Timezone]
UniqueUnique in database#[Unique('users', 'email')]
ExistsExists in database#[Exists('users', 'id')]
use event4u\DataHelpers\SimpleDto\Contracts\ValidationRule;
class EvenNumber implements ValidationRule
{
public function passes(mixed $value): bool
{
return is_int($value) && $value % 2 === 0;
}
public function message(): string
{
return 'The value must be an even number.';
}
}
class NumberDto extends SimpleDto
{
public function __construct(
#[EvenNumber]
public readonly int $number,
) {}
}
// Automatic Laravel validation
class UserController extends Controller
{
public function store(Request $request)
{
$dto = UserDto::validateAndCreate($request->all());
// Automatically uses Laravel's validator
}
}
// Automatic Symfony validation
class UserController
{
public function store(Request $request)
{
$dto = UserDto::validateAndCreate($request->request->all());
// Automatically uses Symfony's validator
}
}

SimpleDto caches validation rules for 198x faster performance:

// First call - builds and caches rules
$dto1 = UserDto::validateAndCreate($data1); // ~10ms
// Subsequent calls - uses cached rules
$dto2 = UserDto::validateAndCreate($data2); // ~0.05ms (198x faster!)
// ✅ Good - multiple validation rules
#[Required, StringType, Min(3), Max(50), Alpha]
public readonly string $name;
// ✅ Good - type hint + validation
#[Required, Between(18, 120)]
public readonly int $age;
// ❌ Bad - no type hint
#[Required, Between(18, 120)]
public readonly $age;
// ✅ Good - validate at creation
$dto = UserDto::validateAndCreate($data);
// ❌ Bad - create then validate
$dto = UserDto::fromArray($data);
$dto->validate();

The following working examples demonstrate this feature:

All examples are fully tested and can be run directly.

The functionality is thoroughly tested. Key test files:

Run the tests:

Terminal window
# Run tests
task test:unit -- --filter=Validation