Nette Documentation Preview

syntax
Schema: validarea datelor
*************************

.[perex]
Bibliotecă practică pentru validarea și normalizarea structurilor de date față de o schemă dată, cu o API inteligentă și ușor de înțeles.

Instalare:

```shell
composer require nette/schema
```


Utilizare de bază
-----------------

În variabila `$schema` avem schema de validare (ce înseamnă exact și cum să creăm o astfel de schemă vom discuta imediat) și în variabila `$data` structura de date pe care dorim să o validăm și să o normalizăm. Poate fi, de exemplu, date trimise de utilizator prin interfața API, un fișier de configurare etc.

Sarcina este gestionată de clasa [api:Nette\Schema\Processor], care procesează intrarea și fie returnează date normalizate, fie, în caz de eroare, aruncă excepția [api:Nette\Schema\ValidationException].

```php
$processor = new Nette\Schema\Processor;

try {
	$normalized = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
	echo 'Datele nu sunt valide: ' . $e->getMessage();
}
```

Metoda `$e->getMessages()` returnează un array al tuturor mesajelor ca șiruri și `$e->getMessageObjects()` returnează toate mesajele ca obiecte "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html.


Definirea schemei
-----------------

Și acum creăm schema. Pentru definirea ei se utilizează clasa [api:Nette\Schema\Expect], definim de fapt așteptările despre cum ar trebui să arate datele. Să spunem că datele de intrare trebuie să formeze o structură (de exemplu, un array) care conține elementele `processRefund` de tip bool și `refundAmount` de tip int.

```php
use Nette\Schema\Expect;

$schema = Expect::structure([
	'processRefund' => Expect::bool(),
	'refundAmount' => Expect::int(),
]);
```

Credem că definiția schemei pare ușor de înțeles, chiar dacă o vedeți pentru prima dată.

Trimitem spre validare următoarele date:

```php
$data = [
	'processRefund' => true,
	'refundAmount' => 17,
];

$normalized = $processor->process($schema, $data); // OK, trece validarea
```

Ieșirea, adică valoarea `$normalized`, este un obiect `stdClass`. Dacă am dori ca ieșirea să fie un array, completăm schema cu conversia `Expect::structure([...])->castTo('array')`.

Toate elementele structurii sunt opționale și au valoarea implicită `null`. Exemplu:

```php
$data = [
	'refundAmount' => 17,
];

$normalized = $processor->process($schema, $data); // OK, trece validarea
// $normalized = {'processRefund' => null, 'refundAmount' => 17}
```

Faptul că valoarea implicită este `null` nu înseamnă că s-ar accepta în datele de intrare `'processRefund' => null`. Nu, intrarea trebuie să fie un boolean, adică doar `true` sau `false`. Pentru a permite `null`, ar trebui să o facem explicit folosind `Expect::bool()->nullable()`.

Elementul poate fi făcut obligatoriu folosind `Expect::bool()->required()`. Valoarea implicită o schimbăm, de exemplu, la `false` folosind `Expect::bool()->default(false)` sau prescurtat `Expect::bool(false)`.

Și ce dacă am dori să acceptăm, pe lângă boolean, și `1` și `0`? Atunci specificăm o enumerare a valorilor, pe care le lăsăm, în plus, să fie normalizate la boolean:

```php
$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 deja elementele de bază ale modului în care se definește o schemă și cum se comportă elementele individuale ale structurii. Acum vom arăta ce alte elemente pot fi utilizate la definirea schemei.


Tipuri de date: type()
----------------------

În schemă se pot specifica toate tipurile de date standard PHP:

```php
Expect::string($default = null)
Expect::int($default = null)
Expect::float($default = null)
Expect::bool($default = null)
Expect::null()
Expect::array($default = [])
```

Și, în plus, toate tipurile [suportate de clasa Validators|utils:validators#Očekávané typy], de exemplu `Expect::type('scalar')` sau prescurtat `Expect::scalar()`. De asemenea, numele claselor sau interfețelor, de exemplu `Expect::type('AddressEntity')`.

Se poate utiliza și notația union:

```php
Expect::type('bool|string|array')
```

Valoarea implicită este întotdeauna `null`, cu excepția `array` și `list`, unde este un array gol. (List este un array indexat conform unei serii ascendente de chei numerice începând de la zero, adică un array neasociativ).


Array-uri de valori: arrayOf() listOf()
---------------------------------------

Array-ul reprezintă o structură prea generală, este mai util să specificăm exact ce elemente poate conține. De exemplu, un array ale cărui elemente pot fi doar șiruri:

```php
$schema = Expect::arrayOf('string');

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
$processor->process($schema, ['key' => 123]); // EROARE: 123 nu este string
```

Al doilea parametru poate specifica cheile (începând cu versiunea 1.2):

```php
$schema = Expect::arrayOf('string', 'int');

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // EROARE: 'a' nu este int
```

List este un array indexat:

```php
$schema = Expect::listOf('string');

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 123]); // EROARE: 123 nu este string
$processor->process($schema, ['key' => 'a']); // EROARE: nu este list
$processor->process($schema, [1 => 'a', 0 => 'b']); // EROARE: de asemenea, nu este list
```

Parametrul poate fi și o schemă, deci putem scrie:

```php
Expect::arrayOf(Expect::bool())
```

Valoarea implicită este un array gol. Dacă specificați o valoare implicită, aceasta va fi combinată cu datele transmise. Acest lucru poate fi dezactivat folosind `mergeDefaults(false)` (începând cu versiunea 1.1).


Enumerare: anyOf()
------------------

`anyOf()` reprezintă o enumerare a valorilor sau schemelor pe care le poate lua o valoare. Astfel scriem un array de elemente care pot fi fie `'a'`, `true` sau `null`:

```php
$schema = Expect::listOf(
	Expect::anyOf('a', true, null),
);

$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // EROARE: false nu aparține aici
```

Elementele enumerării pot fi și scheme:

```php
$schema = Expect::listOf(
	Expect::anyOf(Expect::string(), true, null),
);

$processor->process($schema, ['foo', true, null, 'bar']); // OK
$processor->process($schema, [123]); // EROARE
```

Metoda `anyOf()` acceptă variantele ca parametri individuali, nu un array. Dacă doriți să îi transmiteți un array de valori, utilizați operatorul unpacking `anyOf(...$variants)`.

Valoarea implicită este `null`. Prin metoda `firstIsDefault()` facem primul element implicit:

```php
// implicit este 'hello'
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();
```


Structuri
---------

Structurile sunt obiecte cu chei definite. Fiecare pereche cheie => valoare este denumită „proprietate”:

Structurile acceptă array-uri și obiecte și returnează obiecte `stdClass`.

În mod implicit, toate proprietățile sunt opționale și au valoarea implicită `null`. Puteți defini proprietăți obligatorii folosind `required()`:

```php
$schema = Expect::structure([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // valoarea implicită este null
]);

$processor->process($schema, ['optional' => '']);
// EROARE: opțiunea 'required' lipsește

$processor->process($schema, ['required' => 'foo']);
// OK, returnează {'required' => 'foo', 'optional' => null}
```

Dacă nu doriți să aveți în ieșire proprietăți cu valoare implicită, utilizați `skipDefaults()`:

```php
$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). Proprietățile care acceptă `null` le definim folosind `nullable()`:

```php
$schema = Expect::structure([
	'optional' => Expect::string(),
	'nullable' => Expect::string()->nullable(),
]);

$processor->process($schema, ['optional' => null]);
// EROARE: 'optional' se așteaptă să fie string, null dat.

$processor->process($schema, ['nullable' => null]);
// OK, returnează {'optional' => null, 'nullable' => null}
```

Array-ul tuturor proprietăților structurii este returnat de metoda `getShape()`.

În mod implicit, nu pot exista elemente suplimentare în datele de intrare:

```php
$schema = Expect::structure([
	'key' => Expect::string(),
]);

$processor->process($schema, ['additional' => 1]);
// EROARE: Element neașteptat 'additional'
```

Ceea ce putem schimba folosind `otherItems()`. Ca parametru specificăm schema conform căreia se vor valida elementele suplimentare:

```php
$schema = Expect::structure([
	'key' => Expect::string(),
])->otherItems(Expect::int());

$processor->process($schema, ['additional' => 1]); // OK
$processor->process($schema, ['additional' => true]); // EROARE
```

Puteți crea o nouă structură derivând dintr-o alta folosind `extend()`:

```php
$dog = Expect::structure([
	'name' => Expect::string(),
	'age' => Expect::int(),
]);

$dogWithBreed = $dog->extend([
	'breed' => Expect::string(),
]);
```


Array-uri .{data-version:1.3.2}
-------------------------------

Array cu chei definite. Pentru el se aplică tot ce [structuri|#structure].

```php
$schema = Expect::array([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // valoarea implicită este null
]);
```

Se poate defini și un array indexat, cunoscut sub numele de tuple:

```php
$schema = Expect::array([
	Expect::int(),
	Expect::string(),
	Expect::bool(),
]);

$processor->process($schema, [1, 'hello', true]); // OK
```


Proprietăți învechite
---------------------

Puteți marca o proprietate ca învechită folosind metoda `deprecated([string $message])`. Informațiile despre încetarea suportului sunt returnate folosind `$processor->getWarnings()`:

```php
$schema = Expect::structure([
	'old' => Expect::int()->deprecated('Elementul %path% este învechit'),
]);

$processor->process($schema, ['old' => 1]); // OK
$processor->getWarnings(); // ["Elementul 'old' este învechit"]
```


Intervale: min() max()
----------------------

Folosind `min()` și `max()` se poate limita numărul de elemente la array-uri:

```php
// array, cel puțin 10 elemente, maxim 20 elemente
Expect::array()->min(10)->max(20);
```

La șiruri se poate limita lungimea lor:

```php
// șir, cel puțin 10 caractere lungime, maxim 20 caractere
Expect::string()->min(10)->max(20);
```

La numere se poate limita valoarea lor:

```php
// număr întreg, între 10 și 20 inclusiv
Expect::int()->min(10)->max(20);
```

Desigur, este posibil să specificați doar `min()`, sau doar `max()`:

```php
// șir maxim 20 caractere
Expect::string()->max(20);
```


Expresii regulate: pattern()
----------------------------

Folosind `pattern()`, puteți specifica o expresie regulată căreia trebuie să îi corespundă **întregul** șir de intrare (adică, ca și cum ar fi încadrat de caracterele `^` și `$`):

```php
// exact 9 cifre
Expect::string()->pattern('\d{9}');
```


Restricții personalizate: assert()
----------------------------------

Orice alte restricții le specificăm folosind `assert(callable $fn)`.

```php
$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']); // EROARE: 3 nu este un număr par
```

Sau

```php
Expect::string()->assert('is_file'); // fișierul trebuie să existe
```

La fiecare restricție puteți adăuga o descriere personalizată. Aceasta va face parte din mesajul de eroare.

```php
$schema = Expect::arrayOf('string')
	->assert($countIsEven, 'Elemente pare în array');

$processor->process($schema, ['a', 'b', 'c']);
// Aserțiune eșuată "Elemente pare în array" pentru elementul cu valoarea array.
```

Metoda poate fi apelată în mod repetat pentru a adăuga mai multe restricții. Poate fi intercalată cu apeluri `transform()` și `castTo()`.


Transformări: transform() .{data-version:1.2.5}
-----------------------------------------------

Datele validate cu succes pot fi modificate folosind o funcție personalizată:

```php
// conversie la majuscule:
Expect::string()->transform(fn(string $s) => strtoupper($s));
```

Metoda poate fi apelată în mod repetat pentru a adăuga mai multe transformări. Poate fi intercalată cu apeluri `assert()` și `castTo()`. Operațiile se efectuează în ordinea în care sunt declarate:

```php
Expect::type('string|int')
	->castTo('string')
	->assert('ctype_lower', 'Toate caracterele trebuie să fie minuscule')
	->transform(fn(string $s) => strtoupper($s)); // conversie la majuscule
```

Metoda `transform()` poate transforma și valida simultan valoarea. Acest lucru este adesea mai simplu și mai puțin duplicat decât înlănțuirea `transform()` și `assert()`. În acest scop, funcția primește un obiect [Context |api:Nette\Schema\Context] cu metoda `addError()`, care poate fi utilizată pentru a adăuga informații despre problemele de validare:

```php
Expect::string()
	->transform(function (string $s, Nette\Schema\Context $context) {
		if (!ctype_lower($s)) {
			$context->addError('Toate caracterele trebuie să fie minuscule', 'my.case.error');
			return null;
		}

		return strtoupper($s);
	});
```


Conversie: castTo()
-------------------

Datele validate cu succes pot fi convertite:

```php
Expect::scalar()->castTo('string');
```

Pe lângă tipurile native PHP, se poate converti și la clase. Se face distincție dacă este o clasă simplă fără constructor sau o clasă cu constructor. Dacă clasa nu are constructor, se creează instanța sa și toate elementele structurii se scriu în proprietăți:

```php
class Info
{
	public bool $processRefund;
	public int $refundAmount;
}

Expect::structure([
	'processRefund' => Expect::bool(),
	'refundAmount' => Expect::int(),
])->castTo(Info::class);

// creează '$obj = new Info' și scrie în $obj->processRefund și $obj->refundAmount
```

Dacă clasa are constructor, elementele structurii se transmit ca parametri numiți constructorului:

```php
class Info
{
	public function __construct(
		public bool $processRefund,
		public int $refundAmount,
	) {
	}
}

// creează $obj = new Info(processRefund: ..., refundAmount: ...)
```

Conversia în combinație cu un parametru scalar creează un obiect și transmite valoarea ca singurul parametru al constructorului:

```php
Expect::string()->castTo(DateTime::class);
// creează new DateTime(...)
```


Normalizare: before()
---------------------

Înainte de validarea propriu-zisă, datele pot fi normalizate folosind metoda `before()`. Ca exemplu, să luăm un element care trebuie să fie un array de șiruri (de exemplu, `['a', 'b', 'c']`), dar acceptă intrarea sub forma șirului `a b c`:

```php
$explode = fn($v) => explode(' ', $v);

$schema = Expect::arrayOf('string')
	->before($explode);

$normalized = $processor->process($schema, 'a b c');
// OK și returnează ['a', 'b', 'c']
```


Mapare la obiecte: from()
-------------------------

Schema structurii o putem lăsa să fie generată dintr-o clasă. Exemplu:

```php
class Config
{
	public string $name;
	public string|null $password;
	public bool $admin = false;
}

$schema = Expect::from(new Config);

$data = [
	'name' => 'franta',
];

$normalized = $processor->process($schema, $data);
// $normalized instanceof Config
// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false}
```

Sunt suportate și clasele anonime:

```php
$schema = Expect::from(new class {
	public string $name;
	public ?string $password;
	public bool $admin = false;
});
```

Deoarece informațiile obținute din definiția clasei pot să nu fie suficiente, puteți completa elementele cu o schemă proprie folosind al doilea parametru:

```php
$schema = Expect::from(new Config, [
	'name' => Expect::string()->pattern('\w:.*'),
]);
```


{{leftbar: nette:@menu-topics}}

Schema: validarea datelor

Bibliotecă practică pentru validarea și normalizarea structurilor de date față de 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 schema de validare (ce înseamnă exact și cum să creăm o astfel de schemă vom discuta imediat) și în variabila $data structura de date pe care dorim să o validăm și să o normalizăm. Poate fi, de exemplu, date trimise de utilizator prin interfața API, un fișier de configurare etc.

Sarcina este gestionată de clasa Nette\Schema\Processor, care procesează intrarea și fie returnează date normalizate, fie, în caz de eroare, aruncă excepția Nette\Schema\ValidationException.

$processor = new Nette\Schema\Processor;

try {
	$normalized = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
	echo 'Datele nu sunt valide: ' . $e->getMessage();
}

Metoda $e->getMessages() returnează un array al tuturor mesajelor ca șiruri și $e->getMessageObjects() returnează toate mesajele ca obiecte Nette\Schema\Message.

Definirea schemei

Și acum creăm schema. Pentru definirea ei se utilizează clasa Nette\Schema\Expect, definim de fapt așteptările despre cum ar trebui să arate datele. Să spunem că datele de intrare trebuie să formeze o structură (de exemplu, un array) 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 ușor de înțeles, chiar dacă o vedeți pentru prima dată.

Trimitem spre validare următoarele date:

$data = [
	'processRefund' => true,
	'refundAmount' => 17,
];

$normalized = $processor->process($schema, $data); // OK, trece validarea

Ieșirea, adică valoarea $normalized, este un obiect stdClass. Dacă am dori ca ieșirea să fie un array, completăm schema cu conversia Expect::structure([...])->castTo('array').

Toate elementele structurii sunt opționale și au valoarea implicită null. Exemplu:

$data = [
	'refundAmount' => 17,
];

$normalized = $processor->process($schema, $data); // OK, trece validarea
// $normalized = {'processRefund' => null, 'refundAmount' => 17}

Faptul că valoarea implicită este null nu înseamnă că s-ar accepta în datele de intrare 'processRefund' => null. Nu, intrarea trebuie să fie un boolean, adică doar true sau false. Pentru a permite null, ar trebui să o facem explicit folosind Expect::bool()->nullable().

Elementul poate fi făcut obligatoriu folosind Expect::bool()->required(). Valoarea implicită o schimbăm, de exemplu, la false folosind Expect::bool()->default(false) sau prescurtat Expect::bool(false).

Și ce dacă am dori să acceptăm, pe lângă boolean, și 1 și 0? Atunci specificăm o enumerare a valorilor, pe care le lăsăm, în plus, să fie normalizate 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 deja elementele de bază ale modului în care se definește o schemă și cum se comportă elementele individuale ale structurii. Acum vom arăta ce alte elemente pot fi utilizate la definirea schemei.

Tipuri de date: type()

În schemă se pot specifica toate tipurile de date standard PHP:

Expect::string($default = null)
Expect::int($default = null)
Expect::float($default = null)
Expect::bool($default = null)
Expect::null()
Expect::array($default = [])

Și, în plus, toate tipurile suportate de clasa Validators, de exemplu Expect::type('scalar') sau prescurtat Expect::scalar(). De asemenea, numele claselor sau interfețelor, de exemplu Expect::type('AddressEntity').

Se poate utiliza și notația union:

Expect::type('bool|string|array')

Valoarea implicită este întotdeauna null, cu excepția array și list, unde este un array gol. (List este un array indexat conform unei serii ascendente de chei numerice începând de la zero, adică un array neasociativ).

Array-uri de valori: arrayOf() listOf()

Array-ul reprezintă o structură prea generală, este mai util să specificăm exact ce elemente poate conține. De exemplu, un array ale cărui elemente pot fi doar șiruri:

$schema = Expect::arrayOf('string');

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
$processor->process($schema, ['key' => 123]); // EROARE: 123 nu este string

Al doilea parametru poate specifica cheile (începând cu versiunea 1.2):

$schema = Expect::arrayOf('string', 'int');

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // EROARE: 'a' nu este int

List este un array indexat:

$schema = Expect::listOf('string');

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 123]); // EROARE: 123 nu este string
$processor->process($schema, ['key' => 'a']); // EROARE: nu este list
$processor->process($schema, [1 => 'a', 0 => 'b']); // EROARE: de asemenea, nu este list

Parametrul poate fi și o schemă, deci putem scrie:

Expect::arrayOf(Expect::bool())

Valoarea implicită este un array gol. Dacă specificați o valoare implicită, aceasta va fi combinată cu datele transmise. Acest lucru poate fi dezactivat folosind mergeDefaults(false) (începând cu versiunea 1.1).

Enumerare: anyOf()

anyOf() reprezintă o enumerare a valorilor sau schemelor pe care le poate lua o valoare. Astfel scriem un array de elemente care pot fi fie 'a', true sau null:

$schema = Expect::listOf(
	Expect::anyOf('a', true, null),
);

$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // EROARE: false nu aparține aici

Elementele enumerării pot fi și scheme:

$schema = Expect::listOf(
	Expect::anyOf(Expect::string(), true, null),
);

$processor->process($schema, ['foo', true, null, 'bar']); // OK
$processor->process($schema, [123]); // EROARE

Metoda anyOf() acceptă variantele ca parametri individuali, nu un array. Dacă doriți să îi transmiteți un array de valori, utilizați operatorul unpacking anyOf(...$variants).

Valoarea implicită este null. Prin metoda firstIsDefault() facem primul element implicit:

// implicit este 'hello'
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();

Structuri

Structurile sunt obiecte cu chei definite. Fiecare pereche cheie ⇒ valoare este denumită „proprietate”:

Structurile acceptă array-uri și obiecte și returnează obiecte stdClass.

În mod implicit, toate proprietățile sunt opționale și au valoarea implicită null. Puteți defini proprietăți obligatorii folosind required():

$schema = Expect::structure([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // valoarea implicită este null
]);

$processor->process($schema, ['optional' => '']);
// EROARE: opțiunea 'required' lipsește

$processor->process($schema, ['required' => 'foo']);
// OK, returnează {'required' => 'foo', 'optional' => null}

Dacă nu doriți să aveți în ieșire proprietăți cu 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). Proprietățile care acceptă null le definim folosind nullable():

$schema = Expect::structure([
	'optional' => Expect::string(),
	'nullable' => Expect::string()->nullable(),
]);

$processor->process($schema, ['optional' => null]);
// EROARE: 'optional' se așteaptă să fie string, null dat.

$processor->process($schema, ['nullable' => null]);
// OK, returnează {'optional' => null, 'nullable' => null}

Array-ul 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]);
// EROARE: Element neașteptat 'additional'

Ceea ce putem schimba folosind otherItems(). Ca parametru specificăm schema conform căreia se vor valida elementele suplimentare:

$schema = Expect::structure([
	'key' => Expect::string(),
])->otherItems(Expect::int());

$processor->process($schema, ['additional' => 1]); // OK
$processor->process($schema, ['additional' => true]); // EROARE

Puteți crea o nouă structură derivând dintr-o alta folosind extend():

$dog = Expect::structure([
	'name' => Expect::string(),
	'age' => Expect::int(),
]);

$dogWithBreed = $dog->extend([
	'breed' => Expect::string(),
]);

Array-uri

Array cu chei definite. Pentru el se aplică tot ce structuri.

$schema = Expect::array([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // valoarea implicită este null
]);

Se poate defini și un array indexat, cunoscut sub numele de tuple:

$schema = Expect::array([
	Expect::int(),
	Expect::string(),
	Expect::bool(),
]);

$processor->process($schema, [1, 'hello', true]); // OK

Proprietăți învechite

Puteți marca o proprietate ca învechită folosind metoda deprecated([string $message]). Informațiile despre încetarea suportului sunt returnate folosind $processor->getWarnings():

$schema = Expect::structure([
	'old' => Expect::int()->deprecated('Elementul %path% este învechit'),
]);

$processor->process($schema, ['old' => 1]); // OK
$processor->getWarnings(); // ["Elementul 'old' este învechit"]

Intervale: min() max()

Folosind min() și max() se poate limita numărul de elemente la array-uri:

// array, cel puțin 10 elemente, maxim 20 elemente
Expect::array()->min(10)->max(20);

La șiruri se poate limita lungimea lor:

// șir, cel puțin 10 caractere lungime, maxim 20 caractere
Expect::string()->min(10)->max(20);

La numere se poate limita valoarea lor:

// număr întreg, între 10 și 20 inclusiv
Expect::int()->min(10)->max(20);

Desigur, este posibil să specificați doar min(), sau doar max():

// șir maxim 20 caractere
Expect::string()->max(20);

Expresii regulate: pattern()

Folosind pattern(), puteți specifica o expresie regulată căreia trebuie să îi corespundă întregul șir de intrare (adică, ca și cum ar fi încadrat de caracterele ^ și $):

// exact 9 cifre
Expect::string()->pattern('\d{9}');

Restricții personalizate: assert()

Orice alte restricții le specificăm 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']); // EROARE: 3 nu este un număr par

Sau

Expect::string()->assert('is_file'); // fișierul trebuie să existe

La fiecare restricție puteți adăuga o descriere personalizată. Aceasta va face parte din mesajul de eroare.

$schema = Expect::arrayOf('string')
	->assert($countIsEven, 'Elemente pare în array');

$processor->process($schema, ['a', 'b', 'c']);
// Aserțiune eșuată "Elemente pare în array" pentru elementul cu valoarea array.

Metoda poate fi apelată în mod repetat pentru a adăuga mai multe restricții. Poate fi intercalată cu apeluri transform() și castTo().

Transformări: transform()

Datele validate cu succes pot fi modificate folosind o funcție personalizată:

// conversie la majuscule:
Expect::string()->transform(fn(string $s) => strtoupper($s));

Metoda poate fi apelată în mod repetat pentru a adăuga mai multe transformări. Poate fi intercalată cu apeluri assert() și castTo(). Operațiile se efectuează în ordinea în care sunt declarate:

Expect::type('string|int')
	->castTo('string')
	->assert('ctype_lower', 'Toate caracterele trebuie să fie minuscule')
	->transform(fn(string $s) => strtoupper($s)); // conversie la majuscule

Metoda transform() poate transforma și valida simultan valoarea. Acest lucru este adesea mai simplu și mai puțin duplicat decât înlănțuirea transform() și assert(). În acest scop, funcția primește un obiect Context cu metoda 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('Toate caracterele trebuie să fie minuscule', 'my.case.error');
			return null;
		}

		return strtoupper($s);
	});

Conversie: castTo()

Datele validate cu succes pot fi convertite:

Expect::scalar()->castTo('string');

Pe lângă tipurile native PHP, se poate converti și la clase. Se face distincție dacă este o clasă simplă fără constructor sau o clasă cu constructor. Dacă clasa nu are constructor, se creează instanța sa și toate elementele structurii se scriu în proprietăți:

class Info
{
	public bool $processRefund;
	public int $refundAmount;
}

Expect::structure([
	'processRefund' => Expect::bool(),
	'refundAmount' => Expect::int(),
])->castTo(Info::class);

// creează '$obj = new Info' și scrie în $obj->processRefund și $obj->refundAmount

Dacă clasa are constructor, elementele structurii se transmit ca parametri numiți constructorului:

class Info
{
	public function __construct(
		public bool $processRefund,
		public int $refundAmount,
	) {
	}
}

// creează $obj = new Info(processRefund: ..., refundAmount: ...)

Conversia în combinație cu un parametru scalar creează un obiect și transmite valoarea ca singurul parametru al constructorului:

Expect::string()->castTo(DateTime::class);
// creează new DateTime(...)

Normalizare: before()

Înainte de validarea propriu-zisă, datele pot fi normalizate folosind metoda before(). Ca exemplu, să luăm un element care trebuie să fie un array de șiruri (de exemplu, ['a', 'b', 'c']), dar acceptă intrarea sub forma șirului a b c:

$explode = fn($v) => explode(' ', $v);

$schema = Expect::arrayOf('string')
	->before($explode);

$normalized = $processor->process($schema, 'a b c');
// OK și returnează ['a', 'b', 'c']

Mapare la obiecte: from()

Schema structurii o putem lăsa să fie generată dintr-o clasă. Exemplu:

class Config
{
	public string $name;
	public string|null $password;
	public bool $admin = false;
}

$schema = Expect::from(new Config);

$data = [
	'name' => 'franta',
];

$normalized = $processor->process($schema, $data);
// $normalized instanceof Config
// $normalized = {'name' => 'franta', 'password' => null, 'admin' => false}

Sunt suportate și clasele anonime:

$schema = Expect::from(new class {
	public string $name;
	public ?string $password;
	public bool $admin = false;
});

Deoarece informațiile obținute din definiția clasei pot să nu fie suficiente, puteți completa elementele cu o schemă proprie folosind al doilea parametru:

$schema = Expect::from(new Config, [
	'name' => Expect::string()->pattern('\w:.*'),
]);