Nette Documentation Preview

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

.[perex]
Una biblioteca práctica para la validación y normalización de estructuras de datos con respecto a un esquema dado con una API inteligente y fácil de entender.

Instalación:

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


Uso básico .[#toc-basic-usage]
------------------------------

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

De esta tarea se encarga 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 'Data is invalid: ' . $e->getMessage();
}
```

El método `$e->getMessages()` devuelve un array con todas las cadenas de mensajes y `$e->getMessageObjects()` devuelve todos los mensajes como objetos "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html.


Definición del esquema .[#toc-defining-schema]
----------------------------------------------

Y ahora vamos a crear un esquema. La clase [api:Nette\Schema\Expect] se utiliza para definirlo, en realidad definimos las expectativas de cómo deben ser los datos. Digamos que los datos de entrada deben ser una estructura (por ejemplo un array) que contenga 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 clara, incluso si la ves por primera vez.

Enviemos los siguientes datos para su validación:

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

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

La salida, es decir, el valor `$normalized`, es el objeto `stdClass`. Si queremos que la salida sea un array, añadimos un cast al esquema `Expect::structure([...])->castTo('array')`.

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

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

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

El hecho de que el valor por defecto sea `null` no significa que se aceptaría en los datos de entrada `'processRefund' => null`. No, la entrada debe ser booleana, es decir, sólo `true` o `false`. Tendríamos que permitir explícitamente `null` a través de `Expect::bool()->nullable()`.

Un elemento puede hacerse obligatorio mediante `Expect::bool()->required()`. Cambiamos el valor por defecto a `false` utilizando `Expect::bool()->default(false)` o en breve utilizando `Expect::bool(false)`.

¿Y si quisiéramos aceptar `1` and `0` además de booleanos? Entonces enumeramos los valores permitidos, que también normalizaremos a booleanos:

```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 sabes lo básico de cómo se define el esquema y cómo se comportan los elementos individuales de la estructura. Ahora mostraremos qué otros elementos se pueden utilizar para definir un esquema.


Tipos de datos: type() .[#toc-data-types-type]
----------------------------------------------

Todos los tipos de datos estándar de PHP pueden ser listados en el esquema:

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

Y luego todos los tipos [soportados por los Validadores |utils:validators#Expected Types] a través de `Expect::type('scalar')` o abreviado `Expect::scalar()`. También se aceptan nombres de clases o interfaces, por ejemplo `Expect::type('AddressEntity')`.

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

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

El valor por defecto es siempre `null` excepto para `array` y `list`, donde es un array vacío. (Una lista es una matriz indexada en orden ascendente de claves numéricas a partir de cero, es decir, una matriz no asociativa).


Matriz de valores: arrayOf() listOf() .[#toc-array-of-values-arrayof-listof]
----------------------------------------------------------------------------

El array es una estructura demasiado general, es más útil especificar exactamente qué elementos puede contener. Por ejemplo, un array cuyos elementos sólo 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
```

El segundo parámetro puede utilizarse para especificar 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 int
```

La lista es una matriz indexada:

```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: no es una lista
```

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

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

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


Enumeración: anyOf() .[#toc-enumeration-anyof]
----------------------------------------------

`anyOf()` es un conjunto de valores o esquemas que un valor puede ser. A continuación se muestra cómo escribir una matriz 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 debe estar ahí
```

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 como array. Para pasarle un array de valores, utilice el operador de desempaquetado `anyOf(...$variants)`.

El valor por defecto es `null`. Utilice el método `firstIsDefault()` para que el primer elemento sea el valor predeterminado:

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


Estructuras .[#toc-structures]
------------------------------

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

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

Por defecto, todas las propiedades son opcionales y tienen un valor por defecto de `null`. Puede definir propiedades obligatorias utilizando `required()`:

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

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

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

Si no desea mostrar propiedades con un valor predeterminado, utilice `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 por defecto 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 utilizando `nullable()`:

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

$processor->process($schema, ['optional' => null]);
// ERROR: 'optional' espera ser string, dado null.

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

El array de todas las propiedades de la estructura es devuelto por el método `getShape()`.

Por defecto, no puede haber elementos adicionales en los datos de entrada:

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

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

Lo cual podemos cambiar con `otherItems()`. Como parámetro, especificaremos el esquema para cada elemento extra:

```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 derivando de otra mediante `extend()`:

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

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


Matriz .[#toc-array]
--------------------

Un array con claves definidas. Se aplican las mismas reglas que para [las estructuras |#structure].

```php
$schema = Expect::array([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // default value is null
]);
```

También puedes definir una matriz indexada, conocida como tupla:

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

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


Depreciaciones .[#toc-deprecations]
-----------------------------------

Puedes eliminar una propiedad utilizando el método `deprecated([string $message])` método. Los avisos de desaprobación son devueltos por `$processor->getWarnings()`:

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

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


Rangos: min() max() .[#toc-ranges-min-max]
------------------------------------------

Utilice `min()` y `max()` para limitar el número de elementos de las matrices:

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

Para cadenas, limita su longitud:

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

Para los números, limita su valor:

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

Por supuesto, es posible mencionar sólo `min()`, o sólo `max()`:

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


Expresiones regulares: pattern() .[#toc-regular-expressions-pattern]
--------------------------------------------------------------------

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

```php
// sólo 9 dígitos
Expect::string()->pattern('\d{9}');
```


Aserciones personalizadas: assert() .[#toc-custom-assertions-assert]
--------------------------------------------------------------------

Puede añadir cualquier otra restricción utilizando `assert(callable $fn)`.

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

$schema = Expect::arrayOf('string')
	->assert($countIsEven); // la cuenta debe ser par

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

O:

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

Puede añadir su propia descripción para cada aserción. Formará parte del mensaje de error.

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

$processor->process($schema, ['a', 'b', 'c']);
// Fallo en la aserción "Even items in array" para el elemento con valor array.
```

El método puede llamarse repetidamente para añadir múltiples restricciones. Puede entremezclarse con llamadas a `transform()` y `castTo()`.


Transformación: transform() .[#toc-transformation-transform]
------------------------------------------------------------

Los datos validados correctamente pueden modificarse mediante una función personalizada:

```php
// conversion to uppercase:
Expect::string()->transform(fn(string $s) => strtoupper($s));
```

El método puede llamarse repetidamente para añadir múltiples transformaciones. Puede entremezclarse con llamadas a `assert()` y `castTo()`. Las operaciones se ejecutarán en el orden en que se declaren:

```php
Expect::type('string|int')
	->castTo('string')
	->assert('ctype_lower', 'All characters must be lowercased')
	->transform(fn(string $s) => strtoupper($s)); // conversion to uppercase
```

El método `transform()` puede transformar y validar el valor simultáneamente. Esto suele ser más sencillo y menos redundante que encadenar `transform()` y `assert()`. Para ello, la función recibe un objeto [Context |api:Nette\Schema\Context] con un método `addError()`, que puede utilizarse para añadir información sobre problemas de validación:

```php
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() .[#toc-casting-castto]
----------------------------------------

Los datos validados correctamente pueden ser emitidos:

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

Además de los tipos nativos de PHP, también se puede hacer cast a clases. Distingue si se trata de una clase simple sin constructor o de una clase con constructor. Si la clase no tiene constructor, se crea una instancia de la misma y todos los elementos de la estructura se escriben en sus propiedades:

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

Si la clase tiene un 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,
	) {
	}
}

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

El moldeado combinado con un parámetro escalar crea un objeto y pasa el valor como único parámetro al constructor:

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


Normalización: before() .[#toc-normalization-before]
----------------------------------------------------

Antes de la validación propiamente dicha, los datos pueden normalizarse utilizando el método `before()`. Como ejemplo, tengamos un elemento que debe ser un array de cadenas (ej. `['a', 'b', 'c']`), pero que recibe 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, devuelve ['a', 'b', 'c']
```


Asignación a objetos: from() .[#toc-mapping-to-objects-from]
------------------------------------------------------------

Se puede generar esquema de estructura a partir de la clase. Ejemplo:

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

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 añadir un esquema personalizado para los elementos con 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 biblioteca práctica para la validación y normalización de estructuras de datos con respecto a un esquema dado con una API inteligente y fácil de entender.

Instalación:

composer require nette/schema

Uso básico

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

De esta tarea se encarga 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 'Data is invalid: ' . $e->getMessage();
}

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

Definición del esquema

Y ahora vamos a crear un esquema. La clase Nette\Schema\Expect se utiliza para definirlo, en realidad definimos las expectativas de cómo deben ser los datos. Digamos que los datos de entrada deben ser una estructura (por ejemplo un array) que contenga 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 clara, incluso si la ves por primera vez.

Enviemos los siguientes datos para su validación:

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

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

La salida, es decir, el valor $normalized, es el objeto stdClass. Si queremos que la salida sea un array, añadimos un cast al esquema Expect::structure([...])->castTo('array').

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

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

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

El hecho de que el valor por defecto sea null no significa que se aceptaría en los datos de entrada 'processRefund' => null. No, la entrada debe ser booleana, es decir, sólo true o false. Tendríamos que permitir explícitamente null a través de Expect::bool()->nullable().

Un elemento puede hacerse obligatorio mediante Expect::bool()->required(). Cambiamos el valor por defecto a false utilizando Expect::bool()->default(false) o en breve utilizando Expect::bool(false).

¿Y si quisiéramos aceptar 1 and 0 además de booleanos? Entonces enumeramos los valores permitidos, que también normalizaremos a booleanos:

$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 sabes lo básico de cómo se define el esquema y cómo se comportan los elementos individuales de la estructura. Ahora mostraremos qué otros elementos se pueden utilizar para definir un esquema.

Tipos de datos: type()

Todos los tipos de datos estándar de PHP pueden ser listados en el esquema:

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

Y luego todos los tipos soportados por los Validadores a través de Expect::type('scalar') o abreviado Expect::scalar(). También se aceptan nombres de clases o interfaces, por ejemplo Expect::type('AddressEntity').

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

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

El valor por defecto es siempre null excepto para array y list, donde es un array vacío. (Una lista es una matriz indexada en orden ascendente de claves numéricas a partir de cero, es decir, una matriz no asociativa).

Matriz de valores: arrayOf() listOf()

El array es una estructura demasiado general, es más útil especificar exactamente qué elementos puede contener. Por ejemplo, un array cuyos elementos sólo 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

El segundo parámetro puede utilizarse para especificar 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 int

La lista es una matriz indexada:

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

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

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

El valor por defecto es un array vacío. Si especifica el valor por defecto, se fusionará con los datos pasados. Esto puede desactivarse utilizando mergeDefaults(false) (desde la versión 1.1).

Enumeración: anyOf()

anyOf() es un conjunto de valores o esquemas que un valor puede ser. A continuación se muestra cómo escribir una matriz 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 debe estar ahí

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 como array. Para pasarle un array de valores, utilice el operador de desempaquetado anyOf(...$variants).

El valor por defecto es null. Utilice el método firstIsDefault() para que el primer elemento sea el valor predeterminado:

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

Estructuras

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

Las estructuras aceptan matrices y objetos y devuelven objetos stdClass.

Por defecto, todas las propiedades son opcionales y tienen un valor por defecto de null. Puede definir propiedades obligatorias utilizando required():

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

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

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

Si no desea mostrar propiedades con un valor predeterminado, utilice 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 por defecto 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 utilizando nullable():

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

$processor->process($schema, ['optional' => null]);
// ERROR: 'optional' espera ser string, dado null.

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

El array de todas las propiedades de la estructura es devuelto por el método getShape().

Por defecto, no puede haber elementos adicionales en los datos de entrada:

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

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

Lo cual podemos cambiar con otherItems(). Como parámetro, especificaremos el esquema para cada elemento extra:

$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 derivando de otra mediante extend():

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

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

Matriz

Un array con claves definidas. Se aplican las mismas reglas que para las estructuras.

$schema = Expect::array([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // default value is null
]);

También puedes definir una matriz indexada, conocida como tupla:

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

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

Depreciaciones

Puedes eliminar una propiedad utilizando el método deprecated([string $message]) método. Los avisos de desaprobación son devueltos por $processor->getWarnings():

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

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

Rangos: min() max()

Utilice min() y max() para limitar el número de elementos de las matrices:

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

Para cadenas, limita su longitud:

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

Para los números, limita su valor:

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

Por supuesto, es posible mencionar sólo min(), o sólo max():

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

Expresiones regulares: pattern()

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

// sólo 9 dígitos
Expect::string()->pattern('\d{9}');

Aserciones personalizadas: assert()

Puede añadir cualquier otra restricción utilizando assert(callable $fn).

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

$schema = Expect::arrayOf('string')
	->assert($countIsEven); // la cuenta debe ser par

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

O:

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

Puede añadir su propia descripción para cada aserción. Formará parte del mensaje de error.

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

$processor->process($schema, ['a', 'b', 'c']);
// Fallo en la aserción "Even items in array" para el elemento con valor array.

El método puede llamarse repetidamente para añadir múltiples restricciones. Puede entremezclarse con llamadas a transform() y castTo().

Transformación: transform()

Los datos validados correctamente pueden modificarse mediante una función personalizada:

// conversion to uppercase:
Expect::string()->transform(fn(string $s) => strtoupper($s));

El método puede llamarse repetidamente para añadir múltiples transformaciones. Puede entremezclarse con llamadas a assert() y castTo(). Las operaciones se ejecutarán en el orden en que se declaren:

Expect::type('string|int')
	->castTo('string')
	->assert('ctype_lower', 'All characters must be lowercased')
	->transform(fn(string $s) => strtoupper($s)); // conversion to uppercase

El método transform() puede transformar y validar el valor simultáneamente. Esto suele ser más sencillo y menos redundante que encadenar transform() y assert(). Para ello, la función recibe un objeto Context con un método addError(), que puede utilizarse para añadir información sobre problemas de validación:

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()

Los datos validados correctamente pueden ser emitidos:

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

Además de los tipos nativos de PHP, también se puede hacer cast a clases. Distingue si se trata de una clase simple sin constructor o de una clase con constructor. Si la clase no tiene constructor, se crea una instancia de la misma y todos los elementos de la estructura se escriben en sus propiedades:

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

Si la clase tiene un 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,
	) {
	}
}

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

El moldeado combinado con un parámetro escalar crea un objeto y pasa el valor como único parámetro al constructor:

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

Normalización: before()

Antes de la validación propiamente dicha, los datos pueden normalizarse utilizando el método before(). Como ejemplo, tengamos un elemento que debe ser un array de cadenas (ej. ['a', 'b', 'c']), pero que recibe 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, devuelve ['a', 'b', 'c']

Asignación a objetos: from()

Se puede generar esquema de estructura a partir de la clase. Ejemplo:

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}

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 añadir un esquema personalizado para los elementos con el segundo parámetro:

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