Shema: Potrjevanje podatkov
Praktična knjižnica za potrjevanje in normalizacijo podatkovnih struktur glede na dano shemo s pametnim in razumljivim API-jem.
Namestitev:
composer require nette/schema
Osnovna uporaba
V spremenljivki $schema
imamo shemo potrjevanja (kaj točno to pomeni in kako jo ustvariti, bomo povedali
pozneje), v spremenljivki $data
pa imamo podatkovno strukturo, ki jo želimo potrditi in normalizirati. To so lahko
na primer podatki, ki jih uporabnik pošlje prek vmesnika API, konfiguracijske datoteke itd.
Nalogo opravi razred Nette\Schema\Processor, ki obdela vhodne podatke in vrne normalizirane podatke ali pa ob napaki vrže izjemo Nette\Schema\ValidationException.
$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()
vrne polje vseh nizov sporočil, metoda $e->getMessageObjects()
pa
vrne vsa sporočila kot objekte Nette\Schema\Message.
Opredelitev sheme
Zdaj pa ustvarimo shemo. Za njeno opredelitev uporabljamo razred Nette\Schema\Expect, s katerim pravzaprav definiramo
pričakovanja, kako naj bi bili podatki videti. Recimo, da morajo biti vhodni podatki struktura (npr. polje), ki vsebuje elemente
processRefund
tipa bool in refundAmount
tipa int.
use Nette\Schema\Expect;
$schema = Expect::structure([
'processRefund' => Expect::bool(),
'refundAmount' => Expect::int(),
]);
Verjamemo, da je opredelitev sheme videti jasna, tudi če jo vidite prvič.
V potrditev pošljimo naslednje podatke:
$data = [
'processRefund' => true,
'refundAmount' => 17,
];
$normalized = $processor->process($schema, $data); // V redu, je uspešno.
Izhodni podatek, tj. vrednost $normalized
, je objekt stdClass
. Če želimo, da je izhodni podatek
polje, dodamo cast v shemo Expect::structure([...])->castTo('array')
.
Vsi elementi strukture so neobvezni in imajo privzeto vrednost null
. Primer:
$data = [
'refundAmount' => 17,
];
$normalized = $processor->process($schema, $data); // V redu, je uspešno.
// $normalized = {'processRefund' => null, 'refundAmount' => 17}
Dejstvo, da je privzeta vrednost null
, še ne pomeni, da bi bila sprejeta v vhodnih podatkih
'processRefund' => null
. Ne, vhodni podatki morajo biti logične vrednosti, torej samo true
ali
false
. Izrecno bi morali dovoliti null
prek Expect::bool()->nullable()
.
Element lahko postane obvezen z uporabo Expect::bool()->required()
. Privzeto vrednost spremenimo v
false
z uporabo Expect::bool()->default(false)
ali kratko s Expect::bool(false)
.
Kaj pa, če bi poleg boolov želeli sprejeti tudi 1
and 0
? Potem naštejemo dovoljene vrednosti, ki
jih bomo prav tako normalizirali v 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
Zdaj poznate osnove, kako je shema opredeljena in kako se obnašajo posamezni elementi strukture. Zdaj bomo pokazali, katere vse druge elemente je mogoče uporabiti pri opredeljevanju sheme.
Podatkovni tipi: type()
V shemi so lahko navedeni vsi standardni podatkovni tipi PHP:
Expect::string($default = null)
Expect::int($default = null)
Expect::float($default = null)
Expect::bool($default = null)
Expect::null()
Expect::array($default = [])
Nato pa še vse tipe, ki jih podpirajo validatorji prek
Expect::type('scalar')
ali skrajšano Expect::scalar()
. Sprejemljiva so tudi imena razredov ali
vmesnikov, npr. Expect::type('AddressEntity')
.
Uporabite lahko tudi zapis union:
Expect::type('bool|string|array')
Privzeta vrednost je vedno null
, razen za array
in list
, kjer je to prazno polje.
(Seznam je polje, indeksirano v naraščajočem vrstnem redu številskih ključev od nič, torej neasociativno polje).
Polje vrednosti: arrayOf() listOf()
Polje je preveč splošna struktura, zato je koristneje natančno določiti, katere elemente lahko vsebuje. Na primer polje, katerega elementi so lahko samo nizi:
$schema = Expect::arrayOf('string');
$processor->process($schema, ['hello', 'world']); // V REDU
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
$processor->process($schema, ['key' => 123]); // ERROR: 123 ni niz
Drugi parameter se lahko uporabi za določitev ključev (od različice 1.2):
$schema = Expect::arrayOf('string', 'int');
$processor->process($schema, ['hello', 'world']); // V REDU
$processor->process($schema, ['a' => 'hello']); // NAPAKA: 'a' ni int
Seznam je indeksirano polje:
$schema = Expect::listOf('string');
$processor->process($schema, ['a', 'b']); // V REDU
$processor->process($schema, ['a', 123]); // NAPAKA: 123 ni niz
$processor->process($schema, ['key' => 'a']); // NAPAKA: ni seznam
$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: ni seznam
Parameter je lahko tudi shema, tako da lahko zapišemo:
Expect::arrayOf(Expect::bool())
Privzeta vrednost je prazno polje. Če določite privzeto vrednost, bo združena s posredovanimi podatki. To lahko
onemogočite z uporabo mergeDefaults(false)
(od različice 1.1).
Izštevanje: anyOf()
anyOf()
je nabor vrednosti ali shem, ki jih lahko ima vrednost. Tukaj je prikazano, kako zapisati polje elementov,
ki so lahko 'a'
, true
ali null
:
$schema = Expect::listOf(
Expect::anyOf('a', true, null),
);
$processor->process($schema, ['a', true, null, 'a']); // V REDU
$processor->process($schema, ['a', false]); // ERROR: false ne sodi tja
Elementi naštevanja so lahko tudi sheme:
$schema = Expect::listOf(
Expect::anyOf(Expect::string(), true, null),
);
$processor->process($schema, ['foo', true, null, 'bar']); // V REDU
$processor->process($schema, [123]); // ERROR
Metoda anyOf()
sprejema variante kot posamezne parametre in ne kot polje. Če ji želite posredovati polje
vrednosti, uporabite operator razpakiranja anyOf(...$variants)
.
Privzeta vrednost je null
. Če želite, da je prvi element privzet, uporabite metodo
firstIsDefault()
:
// privzeto je 'hello'
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();
Strukture
Strukture so predmeti z določenimi ključi. Vsak od teh parov ključ ⇒ vrednost se imenuje „lastnost“:
Strukture sprejemajo polja in predmete ter vračajo predmete stdClass
.
Privzeto so vse lastnosti neobvezne in imajo privzeto vrednost null
. Obvezne lastnosti lahko določite z uporabo
required()
:
$schema = Expect::structure([
'required' => Expect::string()->required(),
'optional' => Expect::string(), // zasebna vrednost je nič.
]);
$processor->process($schema, ['optional' => '']);
// ERROR: manjka možnost 'required'
$processor->process($schema, ['required' => 'foo']);
// V redu, vrne {'required' => 'foo', 'optional' => null}
Če ne želite izpisati lastnosti samo s privzeto vrednostjo, uporabite skipDefaults()
:
$schema = Expect::structure([
'required' => Expect::string()->required(),
'optional' => Expect::string(),
])->skipDefaults();
$processor->process($schema, ['required' => 'foo']);
// V redu, vrne {'required' => 'foo'}
Čeprav je null
privzeta vrednost lastnosti optional
, je v vhodnih podatkih ni dovoljeno uporabljati
(vrednost mora biti niz). Lastnosti, ki sprejemajo null
, so opredeljene z uporabo nullable()
:
$schema = Expect::structure([
'optional' => Expect::string(),
'nullable' => Expect::string()->nullable(),
]);
$processor->process($schema, ['optional' => null]);
// ERROR: 'optional' pričakuje, da bo niz, podana je ničla.
$processor->process($schema, ['nullable' => null]);
// V redu, vrne {'optional' => null, 'nullable' => null}
Metoda getShape()
vrne polje vseh lastnosti strukture.
Privzeto je, da v vhodnih podatkih ne sme biti nobenih dodatnih elementov:
$schema = Expect::structure([
'key' => Expect::string(),
]);
$processor->process($schema, ['additional' => 1]);
// ERROR: Nepričakovan element 'additional'
To lahko spremenimo s otherItems()
. Kot parameter bomo določili shemo za vsak dodatni element:
$schema = Expect::structure([
'key' => Expect::string(),
])->otherItems(Expect::int());
$processor->process($schema, ['additional' => 1]); // V REDU
$processor->process($schema, ['additional' => true]); // ERROR
Novo strukturo lahko ustvarite tako, da jo izpeljete iz druge strukture z uporabo extend()
:
$dog = Expect::structure([
'name' => Expect::string(),
'age' => Expect::int(),
]);
$dogWithBreed = $dog->extend([
'breed' => Expect::string(),
]);
Polje
Polje z določenimi ključi. Veljajo enaka pravila kot za strukture.
$schema = Expect::array([
'required' => Expect::string()->required(),
'optional' => Expect::string(), // default value is null
]);
Opredelite lahko tudi indeksirano polje, znano kot tuple:
$schema = Expect::array([
Expect::int(),
Expect::string(),
Expect::bool(),
]);
$processor->process($schema, [1, 'hello', true]); // OK
Deprecations
Lastnost lahko izločite z uporabo deprecated([string $message])
metodo. Obvestila o izločitvi vrne
$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"]
Razponi: min() max()
Uporabite min()
in max()
za omejitev števila elementov za polja:
// polje, vsaj 10 elementov, največ 20 elementov
Expect::array()->min(10)->max(20);
Za nize omejite njihovo dolžino:
// niz, dolg vsaj 10 znakov, največ 20 znakov
Expect::string()->min(10)->max(20);
Pri številkah omejite njihovo vrednost:
// celo število, med 10 in vključno 20
Expect::int()->min(10)->max(20);
Seveda je mogoče navesti samo min()
ali samo max()
:
// niz, največ 20 znakov
Expect::string()->max(20);
Regularni izrazi: pattern()
Z uporabo pattern()
lahko določite regularni izraz, ki mu mora ustrezati celoten vhodni niz (tj. kot da
bi bil ovit v znake ^
a $
):
// samo 9 številk
Expect::string()->pattern('\d{9}');
Trditve po meri: assert()
Vse druge omejitve lahko dodate z uporabo spletne strani assert(callable $fn)
.
$countIsEven = fn($v) => count($v) % 2 === 0;
$schema = Expect::arrayOf('string')
->assert($countIsEven); // število mora biti sodo.
$processor->process($schema, ['a', 'b']); // V REDU
$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 ni sodo
Ali .
Expect::string()->assert('is_file'); // datoteka mora obstajati
Za vsako trditev lahko dodate svoj opis. Ta bo del sporočila o napaki.
$schema = Expect::arrayOf('string')
->assert($countIsEven, 'Even items in array');
$processor->process($schema, ['a', 'b', 'c']);
// Neuspešna trditev "Even items in array" za element z vrednostjo array.
Metodo lahko večkrat pokličete, če želite dodati več omejitev. Lahko se meša s klici na transform()
in
castTo()
.
Transformacija: transform()
Uspešno potrjene podatke lahko spremenite s funkcijo po meri:
// conversion to uppercase:
Expect::string()->transform(fn(string $s) => strtoupper($s));
Metodo je mogoče poklicati večkrat, da se doda več preoblikovanj. Lahko se meša s klici na assert()
in
castTo()
. Operacije se izvedejo v vrstnem redu, v katerem so deklarirane:
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()
lahko hkrati preoblikuje in potrdi vrednost. To je pogosto preprostejše in manj odvečno kot
veriženje metod transform()
in assert()
. V ta namen funkcija prejme objekt Context z metodo addError()
, ki jo lahko
uporabimo za dodajanje informacij o težavah pri potrjevanju:
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);
});
Oddajanje: castTo()
Uspešno potrjene podatke je mogoče oddati:
Expect::scalar()->castTo('string');
Poleg izvornih tipov PHP lahko podatke prelijete tudi v razrede. Razlikuje, ali gre za preprost razred brez konstruktorja ali razred s konstruktorjem. Če razred nima konstruktorja, se ustvari njegov primerek, vsi elementi strukture pa se zapišejo v njegove lastnosti:
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
Če ima razred konstruktor, se elementi strukture konstruktorju posredujejo kot poimenovani parametri:
class Info
{
public function __construct(
public bool $processRefund,
public int $refundAmount,
) {
}
}
// creates $obj = new Info(processRefund: ..., refundAmount: ...)
Casting v kombinaciji s skalarnim parametrom ustvari objekt in konstruktorju posreduje vrednost kot edini parameter:
Expect::string()->castTo(DateTime::class);
// creates new DateTime(...)
Normalizacija: before()
Pred samim preverjanjem lahko podatke normaliziramo z metodo before()
. Kot primer imejmo element, ki mora biti
polje nizov (npr. ['a', 'b', 'c']
), vendar prejme vhodne podatke v obliki niza a b c
:
$explode = fn($v) => explode(' ', $v);
$schema = Expect::arrayOf('string')
->before($explode);
$normalized = $processor->process($schema, 'a b c');
// V redu, vrne ['a', 'b', 'c']
preslikava v predmete: from()
Iz razreda lahko ustvarite strukturno shemo. Primer:
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}
Podprti so tudi anonimni razredi:
$schema = Expect::from(new class {
public string $name;
public ?string $password;
public bool $admin = false;
});
Ker informacije, pridobljene iz definicije razreda, morda ne zadostujejo, lahko z drugim parametrom dodate shemo po meri za elemente:
$schema = Expect::from(new Config, [
'name' => Expect::string()->pattern('\w:.*'),
]);