core

PHP Simple Fast & Secure

View on GitHub

DTO Validation Engine (API + CSRF)

Purpose

PSFS now supports declarative validation directly in DTOs, including optional CSRF enforcement, without depending on Twig Form objects.

This provides:

Core Components

Validation Attributes

Supported attributes under src/base/types/helpers/attributes:

Validation Flow

When validate() is called on a DTO:

  1. Input is hydrated (fromArray() or explicit setters).
  2. Default values are applied.
  3. Unknown fields are checked (strictUnknownFields=true by default).
  4. Per-property constraints are validated:
    • required
    • type
    • enum/values
    • pattern
    • length
    • min/max
  5. If DTO has #[CsrfProtected], CSRF token is validated.
  6. A ValidationResult is returned and can be queried via:
    • isValid()
    • getErrors()
    • getValidationErrors()

Unknown Fields Policy

Default behavior is fail-closed:

This prevents accidental mass-assignment and hidden payload drift.

CSRF in DTOs

CSRF is fully declarative:

Resolution order:

  1. Payload fields (tokenField, tokenKeyField).
  2. Header fallback (X-CSRF-Token by default, configurable via CsrfProtected).

Validation enforces one-time token semantics and expiration using csrf.expiration.

Example DTO

<?php

namespace PSFS\base\dto;

use PSFS\base\types\helpers\attributes\CsrfField;
use PSFS\base\types\helpers\attributes\CsrfProtected;
use PSFS\base\types\helpers\attributes\Length;
use PSFS\base\types\helpers\attributes\Pattern;

#[CsrfProtected(formKey: 'admin_setup')]
#[CsrfField(tokenField: 'admin_setup_token', tokenKeyField: 'admin_setup_token_key')]
class DeleteUserRequestDto extends Dto
{
    /** @required */
    #[Length(min: 1, max: 64)]
    #[Pattern('/^[a-zA-Z0-9._-]+$/')]
    public ?string $username = null;
}

API Usage Pattern

$dto = DeleteUserRequestDto::fromArray($request->getRawData());
$result = $dto->validate(ValidationContext::fromRequest($request));

if (!$result->isValid()) {
    return ApiResponse::error($result->getFirstErrorMessage(), 400, $result->getErrors());
}

Notes:

Backward Compatibility

Migration Strategy

  1. Start with mutation endpoints (create/update/delete).
  2. Introduce DTO per endpoint command.
  3. Add explicit constraints and optional CsrfProtected.
  4. Replace ad-hoc checks in controller/service with validate().
  5. Keep legacy paths temporarily where needed.

Testing Recommendations

Mandatory for each new validated DTO:

Security Notes