This guide explains how to use the Validation module in controllers and application flow.
It assumes:
- The Validation module is available under the project namespace
Maatify\Validation respect/validationis installed- PHP 8.2+
- PHPStan level max compatibility is required
Each endpoint must have one schema representing its input.
Example:
- Login →
AuthLoginSchema - Create Admin →
AdminCreateSchema
use Maatify\Validation\Validator\RespectValidator;
use Maatify\Validation\Schemas\AuthLoginSchema;
use Maatify\Validation\ErrorMapper\SystemApiErrorMapper;
/** @var array<string, mixed> $input */
$input = (array) $request->getParsedBody();
$validator = new RespectValidator();
$schema = new AuthLoginSchema();
$result = $validator->validate($schema, $input);📌 Notes:
- Validation never throws for invalid input
- All errors are structured and typed
- HTTP status is always
400for validation errors (by design —422is reserved for non-validation semantic failures) - ❌ Validation does not perform input sanitization (e.g.,
trim,normalize) – handle sanitization explicitly in the controller or input factory if needed
if (!$result->isValid()) {
$errorMapper = new SystemApiErrorMapper();
$errorResponse = $errorMapper->mapValidationErrors(
$result->getErrors()
);
return $response
->withStatus($errorResponse->getStatus())
->withJson($errorResponse->toArray());
}// Input is valid here
// Call Service / Domain layer safelyAll schemas must extend AbstractSchema.
use Maatify\Validation\Schemas\AbstractSchema;
use Maatify\Validation\Rules\RequiredStringRule;
use Maatify\Validation\Enum\ValidationErrorCodeEnum;
final class ExampleSchema extends AbstractSchema
{
protected function rules(): array
{
return [
'title' => [
RequiredStringRule::rule(3, 100),
ValidationErrorCodeEnum::REQUIRED_FIELD,
],
];
}
}📌 Rules format:
'field_name' => [Validatable, ValidationErrorCodeEnum]Rules are thin wrappers around Respect validators.
Example:
use Respect\Validation\Validator as v;
use Respect\Validation\Validatable;
final class SlugRule
{
/**
* @return Validatable
*/
public static function rule()
{
return v::stringType()->regex('/^[a-z0-9-]+$/');
}
}Rules:
- Must not know about Schemas
- Must not throw custom exceptions
- Must return
Validatable(via docblock)
All validation errors use:
ValidationErrorCodeEnumExample:
ValidationErrorCodeEnum::INVALID_EMAIL❌ Never use strings directly.
Used by Guards (not Validation):
AuthErrorCodeEnumExample:
AuthErrorCodeEnum::STEP_UP_REQUIREDAll errors are converted to API responses through:
SystemApiErrorMapper$errorMapper->mapValidationErrors($errors);$errorMapper->mapAuthError(AuthErrorCodeEnum::NOT_AUTHORIZED);All error responses are returned as:
ApiErrorResponseDTO$errorResponse->getStatus(); // HTTP status code
$errorResponse->toArray(); // API-safe payloadPayload format:
{
"code": "INPUT_INVALID",
"errors": {
"email": ["invalid_email"]
}
}- ❌ Do not validate inside Domain services
- ❌ Do not throw validation exceptions
- ❌ Do not log validation errors
- ❌ Do not return arrays from ErrorMappers
- ❌ Do not use strings instead of Enums
- ❌ Do not mix validation with authorization
| Mistake | Why It’s Wrong |
|---|---|
| Validating in Service | Breaks separation of concerns |
| Using strings for errors | Breaks type-safety |
| try/catch per field | Duplication (use AbstractSchema) |
| HTTP logic in Schema | Schema must be framework-agnostic |
- Return types for Respect validators are declared via docblocks
- This is intentional for PHPStan compatibility
- Do not add strict return types to Rule methods
Every request must be validated using a Schema. Every validation error must be expressed as an Enum. Every error response must be returned as a DTO.
- Usage pattern: LOCKED
- API contract: STABLE
- PHPStan: PASS (level max)