Schema: Validarea datelor
O bibliotecă practică pentru validarea și normalizarea structurilor de date în raport cu o schemă dată, cu o API inteligentă și ușor de înțeles.
Instalare:
composer require nette/schema
Utilizare de bază
În variabila $schema
avem o schemă de validare (ce înseamnă exact acest lucru și cum să o creăm vom spune
mai târziu) și în variabila $data
avem o structură de date pe care dorim să o validăm și să o normalizăm.
Aceasta poate fi, de exemplu, date trimise de utilizator prin intermediul unui API, fișier de configurare etc.
Sarcina este gestionată de clasa Nette\Schema\Processor, care procesează datele de intrare și fie returnează datele normalizate, fie aruncă o excepție Nette\Schema\ValidationException în caz de eroare.
$processor = new Nette\Schema\Processor;
try {
$normalized = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
echo 'Data is invalid: ' . $e->getMessage();
}
Metoda $e->getMessages()
returnează o matrice cu toate șirurile de mesaje, iar
$e->getMessageObjects()
returnează toate mesajele ca obiecte Nette\Schema\Message.
Definirea schemei
Și acum să creăm o schemă. Clasa Nette\Schema\Expect este utilizată pentru a o defini,
definim de fapt așteptările cu privire la modul în care ar trebui să arate datele. Să spunem că datele de intrare trebuie
să fie o structură (de exemplu, o matrice) care conține elementele processRefund
de tip bool și
refundAmount
de tip int.
use Nette\Schema\Expect;
$schema = Expect::structure([
'processRefund' => Expect::bool(),
'refundAmount' => Expect::int(),
]);
Credem că definiția schemei pare clară, chiar dacă o vedeți pentru prima dată.
Să trimitem următoarele date pentru validare:
$data = [
'processRefund' => true,
'refundAmount' => 17,
];
$normalized = $processor->process($schema, $data); // Bine, treisprezece.
Rezultatul, adică valoarea $normalized
, este obiectul stdClass
. Dacă dorim ca ieșirea să fie
o matrice, adăugăm un cast la schema Expect::structure([...])->castTo('array')
.
Toate elementele structurii sunt opționale și au o valoare implicită null
. Exemplu:
$data = [
'refundAmount' => 17,
];
$normalized = $processor->process($schema, $data); // Bine, Trei.
// $normalized = {'processRefund' => null, 'refundAmount' => 17}
Faptul că valoarea implicită este null
nu înseamnă că aceasta ar fi acceptată în datele de intrare
'processRefund' => null
. Nu, datele de intrare trebuie să fie booleene, adică numai true
sau
false
. Ar trebui să permitem în mod explicit null
prin Expect::bool()->nullable()
.
Un element poate fi făcut obligatoriu folosind Expect::bool()->required()
. Schimbăm valoarea implicită la
false
folosind Expect::bool()->default(false)
sau la scurt timp folosind
Expect::bool(false)
.
Și dacă am dori să acceptăm 1
and 0
în afară de booleeni? Atunci enumerăm valorile permise, pe
care le vom normaliza, de asemenea, la boolean:
$schema = Expect::structure([
'processRefund' => Expect::anyOf(true, false, 1, 0)->castTo('bool'),
'refundAmount' => Expect::int(),
]);
$normalized = $processor->process($schema, $data);
is_bool($normalized->processRefund); // true
Acum cunoașteți elementele de bază ale modului în care este definită schema și cum se comportă elementele individuale ale structurii. Vom arăta acum ce alte elemente pot fi utilizate în definirea unei scheme.
Tipuri de date: type()
Toate tipurile de date PHP standard pot fi enumerate în schemă:
Expect::string($default = null)
Expect::int($default = null)
Expect::float($default = null)
Expect::bool($default = null)
Expect::null()
Expect::array($default = [])
Și apoi toate tipurile acceptate de validatoare
prin Expect::type('scalar')
sau prin abrevierea Expect::scalar()
. De asemenea, sunt acceptate și nume
de clase sau interfețe, de exemplu Expect::type('AddressEntity')
.
De asemenea, puteți utiliza notația de uniune:
Expect::type('bool|string|array')
Valoarea implicită este întotdeauna null
, cu excepția array
și list
, unde este
o matrice goală. (O listă este o matrice indexată în ordine crescătoare a cheilor numerice de la zero, adică o matrice
neasociativă).
Array de valori: arrayOf() listOf()
Matricea este o structură prea generală, fiind mai util să se specifice exact ce elemente poate conține. De exemplu, un array ale cărui elemente pot fi doar șiruri de caractere:
$schema = Expect::arrayOf('string');
$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
$processor->process($schema, ['key' => 123]); // ERROR: 123 nu este un șir de caractere
Al doilea parametru poate fi utilizat pentru a specifica cheile (începând cu versiunea 1.2):
$schema = Expect::arrayOf('string', 'int');
$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' nu este int
Lista este o matrice indexată:
$schema = Expect::listOf('string');
$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 123]); // ERROR: 123 nu este un șir de caractere
$processor->process($schema, ['key' => 'a']); // ERROR: nu este o listă
$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: nu este o listă
Parametrul poate fi, de asemenea, o schemă, astfel încât putem scrie:
Expect::arrayOf(Expect::bool())
Valoarea implicită este o matrice goală. Dacă specificați valoarea implicită, aceasta va fi îmbinată cu datele
transmise. Acest lucru poate fi dezactivat utilizând mergeDefaults(false)
(începând cu versiunea 1.1).
Enumerare: anyOf()
anyOf()
este un set de valori sau de scheme care pot reprezenta o valoare. Iată cum se scrie o matrice de
elemente care pot fi fie 'a'
, true
, fie null
:
$schema = Expect::listOf(
Expect::anyOf('a', true, null),
);
$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // ERROR: false nu are ce căuta acolo
Elementele enumerării pot fi, de asemenea, scheme:
$schema = Expect::listOf(
Expect::anyOf(Expect::string(), true, null),
);
$processor->process($schema, ['foo', true, null, 'bar']); // OK
$processor->process($schema, [123]); // ERROR
Metoda anyOf()
acceptă variantele ca parametri individuali, nu ca matrice. Pentru a-i transmite o matrice de
valori, utilizați operatorul de despachetare anyOf(...$variants)
.
Valoarea implicită este null
. Utilizați metoda firstIsDefault()
pentru a face din primul element
valoarea implicită:
// implicit este 'hello'
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();
Structuri
Structurile sunt obiecte cu chei definite. Fiecare dintre aceste perechi cheie ⇒ valoare este denumită “proprietate”:
Structurile acceptă matrici și obiecte și returnează obiecte stdClass
.
În mod implicit, toate proprietățile sunt opționale și au o valoare implicită de null
. Puteți defini
proprietățile obligatorii utilizând required()
:
$schema = Expect::structure([
'required' => Expect::string()->required(),
'optional' => Expect::string(), // valoarea implicită este nulă
]);
$processor->process($schema, ['optional' => '']);
// ERROR: opțiunea 'required' lipsește
$processor->process($schema, ['required' => 'foo']);
// OK, returnează {'required' => 'foo', 'optional' => null}
Dacă nu doriți să afișați proprietăți care au doar o valoare implicită, utilizați skipDefaults()
:
$schema = Expect::structure([
'required' => Expect::string()->required(),
'optional' => Expect::string(),
])->skipDefaults();
$processor->process($schema, ['required' => 'foo']);
// OK, returnează {'required' => 'foo'}
Deși null
este valoarea implicită a proprietății optional
, nu este permisă în datele de intrare
(valoarea trebuie să fie un șir de caractere). Proprietățile care acceptă null
sunt definite utilizând
nullable()
:
$schema = Expect::structure([
'optional' => Expect::string(),
'nullable' => Expect::string()->nullable(),
]);
$processor->process($schema, ['optional' => null]);
// ERROR: 'optional' se așteaptă să fie un șir de caractere, dar este nul.
$processor->process($schema, ['nullable' => null]);
// OK, returnează {'optional' => null, 'nullable' => null}
Matricea tuturor proprietăților structurii este returnată de metoda getShape()
.
În mod implicit, nu pot exista elemente suplimentare în datele de intrare:
$schema = Expect::structure([
'key' => Expect::string(),
]);
$processor->process($schema, ['additional' => 1]);
// ERROR: Element neașteptat "suplimentar
Ceea ce se poate schimba cu otherItems()
. Ca parametru, vom specifica schema pentru fiecare element
suplimentar:
$schema = Expect::structure([
'key' => Expect::string(),
])->otherItems(Expect::int());
$processor->process($schema, ['additional' => 1]); // OK
$processor->process($schema, ['additional' => true]); // ERROR
Puteți crea o structură nouă derivând din alta folosind extend()
:
$dog = Expect::structure([
'name' => Expect::string(),
'age' => Expect::int(),
]);
$dogWithBreed = $dog->extend([
'breed' => Expect::string(),
]);
Array
Un array cu chei definite. Se aplică aceleași reguli ca pentru structuri.
$schema = Expect::array([
'required' => Expect::string()->required(),
'optional' => Expect::string(), // default value is null
]);
De asemenea, puteți defini un array indexat, cunoscut sub numele de tuple:
$schema = Expect::array([
Expect::int(),
Expect::string(),
Expect::bool(),
]);
$processor->process($schema, [1, 'hello', true]); // OK
Deprecieri
Puteți deprecia o proprietate folosind opțiunea deprecated([string $message])
metoda . Notificările de
depreciere sunt returnate de $processor->getWarnings()
:
$schema = Expect::structure([
'old' => Expect::int()->deprecated('The item %path% is deprecated'),
]);
$processor->process($schema, ['old' => 1]); // OK
$processor->getWarnings(); // ["The item 'old' is deprecated"]
Domenii: min() max()
Utilizați min()
și max()
pentru a limita numărul de elemente pentru array-uri:
// matrice, cel puțin 10 elemente, maximum 20 elemente
Expect::array()->min(10)->max(20);
Pentru șirurile de caractere, limitați lungimea acestora:
// șir de cel puțin 10 caractere, maxim 20 de caractere
Expect::string()->min(10)->max(20);
Pentru numere, limitați valoarea acestora:
// număr întreg, între 10 și 20 inclusiv
Expect::int()->min(10)->max(20);
Desigur, este posibil să se menționeze doar min()
, sau doar max()
:
// șir de caractere, maximum 20 de caractere
Expect::string()->max(20);
Expresii regulate: pattern()
Cu ajutorul pattern()
, puteți specifica o expresie regulată cu care trebuie să se potrivească
întregul șir de intrare (adică ca și cum ar fi înfășurat în caractere ^
a $
):
// doar 9 cifre
Expect::string()->pattern('\d{9}');
Aserțiuni personalizate: assert()
Puteți adăuga orice alte restricții folosind assert(callable $fn)
.
$countIsEven = fn($v) => count($v) % 2 === 0;
$schema = Expect::arrayOf('string')
->assert($countIsEven); // numărul trebuie să fie par
$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 nu este par
Sau
Expect::string()->assert('is_file'); // fișierul trebuie să existe
Puteți adăuga propria descriere pentru fiecare afirmație. Aceasta va face parte din mesajul de eroare.
$schema = Expect::arrayOf('string')
->assert($countIsEven, 'Even items in array');
$processor->process($schema, ['a', 'b', 'c']);
// A eșuat aserțiunea "Even items in array" pentru elementul cu valoarea array.
Metoda poate fi apelată în mod repetat pentru a adăuga mai multe constrângeri. Ea poate fi amestecată cu apelurile la
transform()
și castTo()
.
Transformare: transform()
Datele validate cu succes pot fi modificate cu ajutorul unei funcții personalizate:
// conversion to uppercase:
Expect::string()->transform(fn(string $s) => strtoupper($s));
Metoda poate fi apelată în mod repetat pentru a adăuga mai multe transformări. Ea poate fi amestecată cu apeluri la
assert()
și castTo()
. Operațiile vor fi executate în ordinea în care sunt declarate:
Expect::type('string|int')
->castTo('string')
->assert('ctype_lower', 'All characters must be lowercased')
->transform(fn(string $s) => strtoupper($s)); // conversion to uppercase
Metoda transform()
poate transforma și valida valoarea simultan. Acest lucru este adesea mai simplu și mai
puțin redundant decât înlănțuirea transform()
și assert()
. În acest scop, funcția primește un
obiect Context cu o metodă addError()
,
care poate fi utilizată pentru a adăuga informații despre problemele de validare:
Expect::string()
->transform(function (string $s, Nette\Schema\Context $context) {
if (!ctype_lower($s)) {
$context->addError('All characters must be lowercased', 'my.case.error');
return null;
}
return strtoupper($s);
});
Casting: castTo()
Datele validate cu succes pot fi turnate:
Expect::scalar()->castTo('string');
În plus față de tipurile native PHP, puteți, de asemenea, să faceți cast la clase. Se distinge dacă este vorba de o clasă simplă fără constructor sau de o clasă cu constructor. În cazul în care clasa nu are constructor, se creează o instanță a acesteia și toate elementele structurii sunt scrise în proprietățile sale:
class Info
{
public bool $processRefund;
public int $refundAmount;
}
Expect::structure([
'processRefund' => Expect::bool(),
'refundAmount' => Expect::int(),
])->castTo(Info::class);
// creates '$obj = new Info' and writes to $obj->processRefund and $obj->refundAmount
În cazul în care clasa are un constructor, elementele structurii sunt transmise constructorului ca parametri numiți:
class Info
{
public function __construct(
public bool $processRefund,
public int $refundAmount,
) {
}
}
// creates $obj = new Info(processRefund: ..., refundAmount: ...)
Castingul combinat cu un parametru scalar creează un obiect și transmite valoarea acestuia ca unic parametru către constructor:
Expect::string()->castTo(DateTime::class);
// creates new DateTime(...)
Normalizare: before()
Înainte de validarea propriu-zisă, datele pot fi normalizate cu ajutorul metodei before()
. Ca exemplu, să avem
un element care trebuie să fie o matrice de șiruri de caractere (de exemplu ['a', 'b', 'c']
), dar care primește
datele de intrare sub forma unui șir de caractere a b c
:
$explode = fn($v) => explode(' ', $v);
$schema = Expect::arrayOf('string')
->before($explode);
$normalized = $processor->process($schema, 'a b c');
// OK, returnează ['a', 'b', 'c']
Maparea în obiecte: from()
Puteți genera o schemă de structură din clasă. Exemplu:
class Config
{
public string $name;
public string|null $password;
public bool $admin = false;
}
$schema = Expect::from(new Config);
$data = [
'name' => 'jeff',
];
$normalized = $processor->process($schema, $data);
// $normalized instanceof Config
// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false}
Sunt acceptate și clasele anonime:
$schema = Expect::from(new class {
public string $name;
public ?string $password;
public bool $admin = false;
});
Deoarece este posibil ca informațiile obținute din definiția clasei să nu fie suficiente, puteți adăuga o schemă personalizată pentru elemente cu ajutorul celui de-al doilea parametru:
$schema = Expect::from(new Config, [
'name' => Expect::string()->pattern('\w:.*'),
]);