Nette Documentation Preview

syntax
Schema: validación de datos
***************************

.[perex]
Una librería práctica para validar y normalizar estructuras de datos contra un esquema dado con una API inteligente y comprensible.

Instalación:

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


Uso básico
----------

En la variable `$schema` tenemos el esquema de validación (qué significa exactamente y cómo crear dicho esquema lo diremos en breve) y en la variable `$data` la estructura de datos que queremos validar y normalizar. Pueden ser, por ejemplo, datos enviados por el usuario a través de una interfaz API, un archivo de configuración, etc.

La tarea la realiza la clase [api:Nette\Schema\Processor], que procesa la entrada y devuelve los datos normalizados o lanza una excepción [api:Nette\Schema\ValidationException] en caso de error.

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

try {
	$normalized = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
	echo 'Los datos no son válidos: ' . $e->getMessage();
}
```

El método `$e->getMessages()` devuelve un array de todos los mensajes como cadenas y `$e->getMessageObjects()` devuelve todos los mensajes como objetos [api:Nette\Schema\Message].


Definición del esquema
----------------------

Y ahora creemos el esquema. Para definirlo sirve la clase [api:Nette\Schema\Expect], definimos básicamente las expectativas de cómo deben verse los datos. Digamos que los datos de entrada deben formar una estructura (por ejemplo, un array) que contenga los elementos `processRefund` de tipo `bool` y `refundAmount` de tipo `int`.

```php
use Nette\Schema\Expect;

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

Creemos que la definición del esquema parece comprensible, incluso si la ve por primera vez.

Enviemos los siguientes datos para validación:

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

$normalized = $processor->process($schema, $data); // OK, pasa la validación
```

La salida, es decir, el valor `$normalized`, es un objeto `stdClass`. Si quisiéramos que la salida fuera un array, complementaríamos el esquema con la conversión de tipos `Expect::structure([...])->castTo('array')`.

Todos los elementos de la estructura son opcionales y tienen un valor predeterminado de `null`. Ejemplo:

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

$normalized = $processor->process($schema, $data); // OK, pasa la validación
// $normalized = {'processRefund' => null, 'refundAmount' => 17}
```

El hecho de que el valor predeterminado sea `null` no significa que se acepte en los datos de entrada `'processRefund' => null`. No, la entrada debe ser un booleano, es decir, solo `true` o `false`. Tendríamos que permitir `null` explícitamente usando `Expect::bool()->nullable()`.

Un elemento se puede hacer obligatorio usando `Expect::bool()->required()`. Cambiamos el valor predeterminado, por ejemplo, a `false` usando `Expect::bool()->default(false)` o de forma abreviada `Expect::bool(false)`.

¿Y si quisiéramos aceptar `1` y `0` además de booleanos? Entonces especificamos una enumeración de valores, que además dejamos normalizar a booleano:

```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
```

Ahora ya conoce los conceptos básicos de cómo se define un esquema y cómo se comportan los elementos individuales de la estructura. Ahora mostraremos qué otros elementos se pueden usar al definir un esquema.


Tipos de datos: type()
----------------------

En el esquema se pueden especificar todos los tipos de datos estándar de PHP:

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

Y además todos los tipos [soportados por la clase Validators |utils:validators#Tipos esperados], por ejemplo `Expect::type('scalar')` o abreviado `Expect::scalar()`. También nombres de clases o interfaces, por ejemplo `Expect::type('AddressEntity')`.

También se puede usar la notación de unión:

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

El valor predeterminado siempre es `null` excepto para `array` y `list`, donde es un array vacío. (Una lista es un array indexado según una serie ascendente de claves numéricas desde cero, es decir, un array no asociativo).


Arrays de valores: arrayOf() listOf()
-------------------------------------

Un array representa una estructura demasiado general, es más útil especificar exactamente qué elementos puede contener. Por ejemplo, un array cuyos elementos solo pueden ser cadenas:

```php
$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 no es una cadena
```

Con el segundo parámetro se pueden especificar las claves (desde la versión 1.2):

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

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' no es un int
```

Una lista es un array indexado:

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

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 123]); // ERROR: 123 no es una cadena
$processor->process($schema, ['key' => 'a']); // ERROR: no es una lista
$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: tampoco es una lista
```

El parámetro también puede ser un esquema, por lo que podemos escribir:

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

El valor predeterminado es un array vacío. Si especifica un valor predeterminado, se fusionará con los datos pasados. Esto se puede desactivar usando `mergeDefaults(false)` (desde la versión 1.1).


Enumeración: anyOf()
--------------------

`anyOf()` representa una enumeración de valores o esquemas que puede tomar un valor. Así escribimos un array de elementos que pueden ser `'a'`, `true` o `null`:

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

$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // ERROR: false no pertenece allí
```

Los elementos de la enumeración también pueden ser esquemas:

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

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

El método `anyOf()` acepta variantes como parámetros individuales, no un array. Si desea pasarle un array de valores, use el operador de desempaquetado `anyOf(...$variants)`.

El valor predeterminado es `null`. Con el método `firstIsDefault()` hacemos que el primer elemento sea el predeterminado:

```php
// el predeterminado es 'hello'
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();
```


Estructuras
-----------

Las estructuras son objetos con claves definidas. Cada uno de los pares clave => valor se denomina „propiedad“:

Las estructuras aceptan arrays y objetos y devuelven objetos `stdClass`.

De forma predeterminada, todas las propiedades son opcionales y tienen un valor predeterminado de `null`. Puede definir propiedades obligatorias usando `required()`:

```php
$schema = Expect::structure([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // el valor predeterminado es null
]);

$processor->process($schema, ['optional' => '']);
// ERROR: la opción 'required' falta

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

Si no desea tener propiedades con el valor predeterminado en la salida, use `skipDefaults()`:

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

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

Aunque `null` es el valor predeterminado de la propiedad `optional`, no está permitido en los datos de entrada (el valor debe ser una cadena). Las propiedades que aceptan `null` se definen usando `nullable()`:

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

$processor->process($schema, ['optional' => null]);
// ERROR: 'optional' espera ser una cadena, se dio null.

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

El método `getShape()` devuelve un array de todas las propiedades de la estructura.

De forma predeterminada, no puede haber elementos adicionales en los datos de entrada:

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

$processor->process($schema, ['additional' => 1]);
// ERROR: Elemento inesperado 'additional'
```

Lo cual podemos cambiar usando `otherItems()`. Como parámetro, especificamos un esquema según el cual se validarán los elementos adicionales:

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

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

Puede crear una nueva estructura derivándola de otra usando `extend()`:

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

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


Arrays .{data-version:1.3.2}
----------------------------

Arrays con claves definidas. Todo lo que se aplica a las [#estructuras] también se aplica a ellos.

```php
$schema = Expect::array([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // el valor predeterminado es null
]);
```

También se puede definir un array indexado, conocido como tupla:

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

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


Propiedades obsoletas
---------------------

Puede marcar una propiedad como obsoleta usando el método `deprecated([string $message])`. La información sobre el fin del soporte se devuelve mediante `$processor->getWarnings()`:

```php
$schema = Expect::structure([
	'old' => Expect::int()->deprecated('El elemento %path% está obsoleto'),
]);

$processor->process($schema, ['old' => 1]); // OK
$processor->getWarnings(); // ["El elemento 'old' está obsoleto"]
```


Rangos: min() max()
-------------------

Con `min()` y `max()` se puede limitar el número de elementos en los arrays:

```php
// array, al menos 10 elementos, máximo 20 elementos
Expect::array()->min(10)->max(20);
```

En las cadenas, limitar su longitud:

```php
// cadena, al menos 10 caracteres de longitud, máximo 20 caracteres
Expect::string()->min(10)->max(20);
```

En los números, limitar su valor:

```php
// número entero, entre 10 y 20 inclusive
Expect::int()->min(10)->max(20);
```

Por supuesto, es posible especificar solo `min()`, o solo `max()`:

```php
// cadena máximo 20 caracteres
Expect::string()->max(20);
```


Expresiones regulares: pattern()
--------------------------------

Con `pattern()` se puede especificar una expresión regular con la que debe coincidir **toda** la cadena de entrada (es decir, como si estuviera envuelta en los caracteres `^` y `$`):

```php
// exactamente 9 números
Expect::string()->pattern('\d{9}');
```


Restricciones personalizadas: assert()
--------------------------------------

Cualquier otra restricción se especifica usando `assert(callable $fn)`.

```php
$countIsEven = fn($v) => count($v) % 2 === 0;

$schema = Expect::arrayOf('string')
	->assert($countIsEven); // el número debe ser par

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 no es un número par
```

O

```php
Expect::string()->assert('is_file'); // el archivo debe existir
```

A cada restricción puede agregarle su propia descripción. Esta formará parte del mensaje de error.

```php
$schema = Expect::arrayOf('string')
	->assert($countIsEven, 'Even items in array');

$processor->process($schema, ['a', 'b', 'c']);
// Failed assertion "Even items in array" for item with value array.
```

El método se puede llamar repetidamente para agregar más restricciones. Se puede intercalar con llamadas a `transform()` y `castTo()`.


Transformaciones: transform() .{data-version:1.2.5}
---------------------------------------------------

Los datos validados con éxito se pueden modificar usando una función personalizada:

```php
// conversión a mayúsculas:
Expect::string()->transform(fn(string $s) => strtoupper($s));
```

El método se puede llamar repetidamente para agregar más transformaciones. Se puede intercalar con llamadas a `assert()` y `castTo()`. Las operaciones se realizan en el orden en que se declaran:

```php
Expect::type('string|int')
	->castTo('string')
	->assert('ctype_lower', 'Todos los caracteres deben estar en minúsculas')
	->transform(fn(string $s) => strtoupper($s)); // conversión a mayúsculas
```

El método `transform()` puede transformar y validar simultáneamente el valor. Esto suele ser más simple y menos duplicado que encadenar `transform()` y `assert()`. Para este propósito, la función recibe un objeto [Context |api:Nette\Schema\Context] con el método `addError()`, que se puede usar para agregar información sobre problemas de validación:

```php
Expect::string()
	->transform(function (string $s, Nette\Schema\Context $context) {
		if (!ctype_lower($s)) {
			$context->addError('Todos los caracteres deben estar en minúsculas', 'my.case.error');
			return null;
		}

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


Conversión de tipos: castTo()
-----------------------------

Los datos validados con éxito se pueden convertir de tipo:

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

Además de los tipos nativos de PHP, también se puede convertir a clases. Aquí se distingue si se trata de una clase simple sin constructor o una clase con constructor. Si la clase no tiene constructor, se crea su instancia y todos los elementos de la estructura se escriben en las propiedades:

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

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

// crea '$obj = new Info' y escribe en $obj->processRefund y $obj->refundAmount
```

Si la clase tiene constructor, los elementos de la estructura se pasan como parámetros con nombre al constructor:

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

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

La conversión de tipos en combinación con un parámetro escalar crea un objeto y pasa el valor como único parámetro al constructor:

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


Normalización: before()
-----------------------

Antes de la validación misma, los datos se pueden normalizar usando el método `before()`. Como ejemplo, mencionemos un elemento que debe ser un array de cadenas (por ejemplo, `['a', 'b', 'c']`), pero acepta la entrada en forma de cadena `a b c`:

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

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

$normalized = $processor->process($schema, 'a b c');
// OK y devuelve ['a', 'b', 'c']
```


Mapeo a objetos: from()
-----------------------

Podemos hacer que el esquema de la estructura se genere a partir de una clase. Ejemplo:

```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}
```

También se admiten clases anónimas:

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

Dado que la información obtenida de la definición de la clase puede no ser suficiente, puede complementar los elementos con su propio esquema usando el segundo parámetro:

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


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

Schema: validación de datos

Una librería práctica para validar y normalizar estructuras de datos contra un esquema dado con una API inteligente y comprensible.

Instalación:

composer require nette/schema

Uso básico

En la variable $schema tenemos el esquema de validación (qué significa exactamente y cómo crear dicho esquema lo diremos en breve) y en la variable $data la estructura de datos que queremos validar y normalizar. Pueden ser, por ejemplo, datos enviados por el usuario a través de una interfaz API, un archivo de configuración, etc.

La tarea la realiza la clase Nette\Schema\Processor, que procesa la entrada y devuelve los datos normalizados o lanza una excepción Nette\Schema\ValidationException en caso de error.

$processor = new Nette\Schema\Processor;

try {
	$normalized = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
	echo 'Los datos no son válidos: ' . $e->getMessage();
}

El método $e->getMessages() devuelve un array de todos los mensajes como cadenas y $e->getMessageObjects() devuelve todos los mensajes como objetos Nette\Schema\Message.

Definición del esquema

Y ahora creemos el esquema. Para definirlo sirve la clase Nette\Schema\Expect, definimos básicamente las expectativas de cómo deben verse los datos. Digamos que los datos de entrada deben formar una estructura (por ejemplo, un array) que contenga los elementos processRefund de tipo bool y refundAmount de tipo int.

use Nette\Schema\Expect;

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

Creemos que la definición del esquema parece comprensible, incluso si la ve por primera vez.

Enviemos los siguientes datos para validación:

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

$normalized = $processor->process($schema, $data); // OK, pasa la validación

La salida, es decir, el valor $normalized, es un objeto stdClass. Si quisiéramos que la salida fuera un array, complementaríamos el esquema con la conversión de tipos Expect::structure([...])->castTo('array').

Todos los elementos de la estructura son opcionales y tienen un valor predeterminado de null. Ejemplo:

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

$normalized = $processor->process($schema, $data); // OK, pasa la validación
// $normalized = {'processRefund' => null, 'refundAmount' => 17}

El hecho de que el valor predeterminado sea null no significa que se acepte en los datos de entrada 'processRefund' => null. No, la entrada debe ser un booleano, es decir, solo true o false. Tendríamos que permitir null explícitamente usando Expect::bool()->nullable().

Un elemento se puede hacer obligatorio usando Expect::bool()->required(). Cambiamos el valor predeterminado, por ejemplo, a false usando Expect::bool()->default(false) o de forma abreviada Expect::bool(false).

¿Y si quisiéramos aceptar 1 y 0 además de booleanos? Entonces especificamos una enumeración de valores, que además dejamos normalizar a booleano:

$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

Ahora ya conoce los conceptos básicos de cómo se define un esquema y cómo se comportan los elementos individuales de la estructura. Ahora mostraremos qué otros elementos se pueden usar al definir un esquema.

Tipos de datos: type()

En el esquema se pueden especificar todos los tipos de datos estándar de PHP:

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

Y además todos los tipos soportados por la clase Validators, por ejemplo Expect::type('scalar') o abreviado Expect::scalar(). También nombres de clases o interfaces, por ejemplo Expect::type('AddressEntity').

También se puede usar la notación de unión:

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

El valor predeterminado siempre es null excepto para array y list, donde es un array vacío. (Una lista es un array indexado según una serie ascendente de claves numéricas desde cero, es decir, un array no asociativo).

Arrays de valores: arrayOf() listOf()

Un array representa una estructura demasiado general, es más útil especificar exactamente qué elementos puede contener. Por ejemplo, un array cuyos elementos solo pueden ser cadenas:

$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 no es una cadena

Con el segundo parámetro se pueden especificar las claves (desde la versión 1.2):

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

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' no es un int

Una lista es un array indexado:

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

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 123]); // ERROR: 123 no es una cadena
$processor->process($schema, ['key' => 'a']); // ERROR: no es una lista
$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: tampoco es una lista

El parámetro también puede ser un esquema, por lo que podemos escribir:

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

El valor predeterminado es un array vacío. Si especifica un valor predeterminado, se fusionará con los datos pasados. Esto se puede desactivar usando mergeDefaults(false) (desde la versión 1.1).

Enumeración: anyOf()

anyOf() representa una enumeración de valores o esquemas que puede tomar un valor. Así escribimos un array de elementos que pueden ser 'a', true o null:

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

$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // ERROR: false no pertenece allí

Los elementos de la enumeración también pueden ser esquemas:

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

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

El método anyOf() acepta variantes como parámetros individuales, no un array. Si desea pasarle un array de valores, use el operador de desempaquetado anyOf(...$variants).

El valor predeterminado es null. Con el método firstIsDefault() hacemos que el primer elemento sea el predeterminado:

// el predeterminado es 'hello'
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();

Estructuras

Las estructuras son objetos con claves definidas. Cada uno de los pares clave ⇒ valor se denomina „propiedad“:

Las estructuras aceptan arrays y objetos y devuelven objetos stdClass.

De forma predeterminada, todas las propiedades son opcionales y tienen un valor predeterminado de null. Puede definir propiedades obligatorias usando required():

$schema = Expect::structure([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // el valor predeterminado es null
]);

$processor->process($schema, ['optional' => '']);
// ERROR: la opción 'required' falta

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

Si no desea tener propiedades con el valor predeterminado en la salida, use skipDefaults():

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

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

Aunque null es el valor predeterminado de la propiedad optional, no está permitido en los datos de entrada (el valor debe ser una cadena). Las propiedades que aceptan null se definen usando nullable():

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

$processor->process($schema, ['optional' => null]);
// ERROR: 'optional' espera ser una cadena, se dio null.

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

El método getShape() devuelve un array de todas las propiedades de la estructura.

De forma predeterminada, no puede haber elementos adicionales en los datos de entrada:

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

$processor->process($schema, ['additional' => 1]);
// ERROR: Elemento inesperado 'additional'

Lo cual podemos cambiar usando otherItems(). Como parámetro, especificamos un esquema según el cual se validarán los elementos adicionales:

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

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

Puede crear una nueva estructura derivándola de otra usando extend():

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

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

Arrays

Arrays con claves definidas. Todo lo que se aplica a las estructuras también se aplica a ellos.

$schema = Expect::array([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // el valor predeterminado es null
]);

También se puede definir un array indexado, conocido como tupla:

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

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

Propiedades obsoletas

Puede marcar una propiedad como obsoleta usando el método deprecated([string $message]). La información sobre el fin del soporte se devuelve mediante $processor->getWarnings():

$schema = Expect::structure([
	'old' => Expect::int()->deprecated('El elemento %path% está obsoleto'),
]);

$processor->process($schema, ['old' => 1]); // OK
$processor->getWarnings(); // ["El elemento 'old' está obsoleto"]

Rangos: min() max()

Con min() y max() se puede limitar el número de elementos en los arrays:

// array, al menos 10 elementos, máximo 20 elementos
Expect::array()->min(10)->max(20);

En las cadenas, limitar su longitud:

// cadena, al menos 10 caracteres de longitud, máximo 20 caracteres
Expect::string()->min(10)->max(20);

En los números, limitar su valor:

// número entero, entre 10 y 20 inclusive
Expect::int()->min(10)->max(20);

Por supuesto, es posible especificar solo min(), o solo max():

// cadena máximo 20 caracteres
Expect::string()->max(20);

Expresiones regulares: pattern()

Con pattern() se puede especificar una expresión regular con la que debe coincidir toda la cadena de entrada (es decir, como si estuviera envuelta en los caracteres ^ y $):

// exactamente 9 números
Expect::string()->pattern('\d{9}');

Restricciones personalizadas: assert()

Cualquier otra restricción se especifica usando assert(callable $fn).

$countIsEven = fn($v) => count($v) % 2 === 0;

$schema = Expect::arrayOf('string')
	->assert($countIsEven); // el número debe ser par

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 no es un número par

O

Expect::string()->assert('is_file'); // el archivo debe existir

A cada restricción puede agregarle su propia descripción. Esta formará parte del mensaje de error.

$schema = Expect::arrayOf('string')
	->assert($countIsEven, 'Even items in array');

$processor->process($schema, ['a', 'b', 'c']);
// Failed assertion "Even items in array" for item with value array.

El método se puede llamar repetidamente para agregar más restricciones. Se puede intercalar con llamadas a transform() y castTo().

Transformaciones: transform()

Los datos validados con éxito se pueden modificar usando una función personalizada:

// conversión a mayúsculas:
Expect::string()->transform(fn(string $s) => strtoupper($s));

El método se puede llamar repetidamente para agregar más transformaciones. Se puede intercalar con llamadas a assert() y castTo(). Las operaciones se realizan en el orden en que se declaran:

Expect::type('string|int')
	->castTo('string')
	->assert('ctype_lower', 'Todos los caracteres deben estar en minúsculas')
	->transform(fn(string $s) => strtoupper($s)); // conversión a mayúsculas

El método transform() puede transformar y validar simultáneamente el valor. Esto suele ser más simple y menos duplicado que encadenar transform() y assert(). Para este propósito, la función recibe un objeto Context con el método addError(), que se puede usar para agregar información sobre problemas de validación:

Expect::string()
	->transform(function (string $s, Nette\Schema\Context $context) {
		if (!ctype_lower($s)) {
			$context->addError('Todos los caracteres deben estar en minúsculas', 'my.case.error');
			return null;
		}

		return strtoupper($s);
	});

Conversión de tipos: castTo()

Los datos validados con éxito se pueden convertir de tipo:

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

Además de los tipos nativos de PHP, también se puede convertir a clases. Aquí se distingue si se trata de una clase simple sin constructor o una clase con constructor. Si la clase no tiene constructor, se crea su instancia y todos los elementos de la estructura se escriben en las propiedades:

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

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

// crea '$obj = new Info' y escribe en $obj->processRefund y $obj->refundAmount

Si la clase tiene constructor, los elementos de la estructura se pasan como parámetros con nombre al constructor:

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

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

La conversión de tipos en combinación con un parámetro escalar crea un objeto y pasa el valor como único parámetro al constructor:

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

Normalización: before()

Antes de la validación misma, los datos se pueden normalizar usando el método before(). Como ejemplo, mencionemos un elemento que debe ser un array de cadenas (por ejemplo, ['a', 'b', 'c']), pero acepta la entrada en forma de cadena a b c:

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

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

$normalized = $processor->process($schema, 'a b c');
// OK y devuelve ['a', 'b', 'c']

Mapeo a objetos: from()

Podemos hacer que el esquema de la estructura se genere a partir de una clase. Ejemplo:

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}

También se admiten clases anónimas:

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

Dado que la información obtenida de la definición de la clase puede no ser suficiente, puede complementar los elementos con su propio esquema usando el segundo parámetro:

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