Salt la conținut

Validare

Sistemul de validare din Leto verifică și curăță datele de intrare ($_POST, $_GET, orice array) pe baza unor reguli declarate ca string-uri, similar cu Laravel. Clasa principală este Leto\Data\Validation\Validator.

Aceasta este validarea datelor de intrare (request). Nu o confunda cu trait-ul ORM HasValidation, care validează invarianții unui model la salvare. Ambele aruncă ValidationFailedException, dar operează pe straturi diferite: Validator pe input brut, HasValidation pe un model deja construit.

Utilizare de bază

use Leto\Data\Validation\Validator;

$data = Validator::validate($_POST, [
    'name' => 'required|trim',
    'email' => 'required|email',
    'age' => 'default:18|int',
]);

Metoda validate() returnează array-ul cu datele validate și modificate. Dacă validarea eșuează, aruncă ValidationFailedException.

Ordinea modificatorilor contează. Regulile se aplică strict în ordinea în care le scrii. Pune default:... înaintea conversiei de tip, nu după. Cu int|default:18, când câmpul lipsește, int primește null (rezultă null), apoi default întoarce valoarea implicită ca string "18". Cu default:18|int obții corect int(18).

Din GET și POST

// Date din query string
$filters = Validator::validateGet([
    'p' => 'default:1|int',
    'rpp' => 'default:50|int',
    'search' => 'nullable|trim',
]);

// Date din formular
$data = Validator::validatePost([
    'name' => 'required|trim',
    'email' => 'required|email',
]);

Reguli de existență

Controlează dacă un câmp trebuie validat sau poate fi omis:

RegulăEfect
requiredCâmpul trebuie să existe și să nu fie gol
nullableCâmpul poate lipsi sau fi null; în acest caz restul regulilor sunt sărite, iar cheia rămâne în rezultat cu valoarea null
optionalCâmpul poate lipsi complet; dacă lipsește, cheia este omisă din rezultat
Validator::validate($data, [
    'name' => 'required',
    'bio' => 'nullable|max:500',
    'avatar' => 'optional|url',
]);

nullable vs. optional. Ambele fac câmpul opțional, dar diferă prin ce ajunge în array-ul returnat:

  • Folosește nullable când vrei ca cheia să existe în rezultat, cu valoarea null (util pentru câmpuri care se scriu ca NULL în baza de date).
  • Folosește optional când vrei ca cheia să lipsească dacă utilizatorul nu a trimis-o (util pentru filtre construite dinamic).

Atenție: nullable sare peste restul regulilor doar dacă valoarea lipsește sau este null. Un string gol '' nu este sărit, deci nullable|min:3 pe '' tot eșuează. Dacă vrei să tratezi și string-ul gol ca absent, adaugă null_on_empty.

Reguli de modificare

Modifică valoarea înainte de validare. Se aplică în ordinea în care sunt declarate:

RegulăEfect
trimElimină spațiile de la început și sfârșit
int / integerConversie la int
floatConversie la float
decimalConversie la Decimal\Decimal (precizie arbitrară, din extensia ext-decimal). Acceptă opțional precizia și scala: decimal:10,2
bool / booleanConversie la bool
dateConversie la DateTimeImmutable
arrayConversie la array
array_of:TipArray cu elemente convertite la un tip scalar: int, float sau bool (ex: array_of:int). Alt tip aruncă \ValueError
null_on_emptyTransformă orice valoare goală (empty(): '', '0', 0, false, []) în null
default:valoareÎnlocuiește valoarea cu implicita ori de câte ori e goală (empty()), nu doar când lipsește
phone:ROParsează număr de telefon (cod țară opțional)
urlValidează formatul URL (nu normalizează valoarea)
ipv4Asigură format IPv4
ipv6Asigură format IPv6
nin:ROValidează un CNP românesc; valoarea rămâne neschimbată. Necesită argumentul de țară
$data = Validator::validate($_POST, [
    'age' => 'default:0|int',
    'price' => 'decimal:10,2',
    'bio' => 'nullable|null_on_empty|trim',
    'phone' => 'nullable|phone:RO',
    'active' => 'bool',
]);

default acționează pe valori goale, nu doar lipsă. Sub capotă folosește empty(), deci un 0, '0', '', false sau [] legitim trimis de utilizator este înlocuit cu valoarea implicită. Dacă 0 este o intrare validă (ex: o cantitate), nu te baza pe default ca să o păstrezi.

nin necesită țara. Scris fără argument (nin simplu) aruncă \ValueError și oprește request-ul, nu produce o eroare de validare. Folosește întotdeauna nin:RO.

Reguli de validare

Verifică valoarea după ce s-au aplicat modificatorii:

RegulăEfect
emailValidează format email
numericVerifică să fie numeric
not_emptyNu poate fi gol
max:100Lungime maximă (string), valoare maximă (numeric) sau număr maxim de elemente (array)
min:3Lungime minimă (string), valoare minimă (numeric) sau număr minim de elemente (array)
size:10Lungime / valoare / număr de elemente exact
enum:val1,val2,val3Valoarea trebuie să fie una din lista dată
Validator::validate($data, [
    'username' => 'required|trim|min:3|max:50',
    'email' => 'required|email',
    'score' => 'int|min:0|max:100',
    'status' => 'required|enum:active,inactive,banned',
]);

Gestionarea erorilor

use Leto\Data\Validation\Exceptions\ValidationFailedException;

try {
    $data = Validator::validate($_POST, [
        'email' => 'required|email',
        'name' => 'required|min:3',
    ]);
} catch (ValidationFailedException $e) {
    $errors = $e->getErrors();  // ['email' => ['...'], 'name' => ['...']]
}
Atenție! Validator::validate() aruncă \ValueError (nu ValidationFailedException) când încerci să validezi un câmp care nu există în datele de intrare și nu are o regulă de existență (required, nullable sau optional). Un bloc try/catch(ValidationFailedException) nu va prinde această eroare: este un crash, nu o eroare de validare. Asigură-te că toate câmpurile din schema de validare au o regulă de existență.

Exemplu real: filtrare cu paginare

$data = Validator::validateGet([
    'rpp' => 'default:50|int',
    'p' => 'default:1|int',
]);

$jobs = JobModel::all()
    ->orderBy('id', Order::DESC)
    ->paginate($data['p'], $data['rpp'], $paginationInfo);

Exemplu real: login cu stare din sesiune

$data = Validator::validate($_SESSION['auth'] ?? [], [
    'redirectURL' => 'nullable|null_on_empty|url',
    'activeProvider' => 'nullable|null_on_empty|int',
    'identifiedUser' => 'nullable|null_on_empty|int',
]);