Nette Documentation Preview

syntax
Schema: валидация данных
************************

.[perex]
Практичная библиотека для проверки и нормализации структур данных по заданной схеме с интеллектуальным и простым в понимании API.

Установка:

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


Использование
-------------

В переменной `$schema` у нас есть схема валидации (что именно это значит и как её создать, мы расскажем позже), а в переменной `$data` у нас есть структура данных, которую мы хотим валидировать и нормализовать. Это могут быть, например, данные, отправленные пользователем через API, конфигурационный файл и т. д.

Задачей занимается класс [api:Nette\Schema\Processor], который обрабатывает входные данные и либо возвращает нормализованные данные, либо выбрасывает исключение [api:Nette\Schema\ValidationException] при ошибке.

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

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

Метод `$e->getMessages()` возвращает массив всех строк сообщений, а `$e->getMessageObjects()` возвращает все сообщения в виде объектов "Nette\Schema\Message":https://api.nette.org/schema/master/Nette/Schema/Message.html.


Определение схемы
-----------------

А теперь давайте создадим схему. С помощью класса [api:Nette\Schema\Expect] мы фактически определяем, как должны выглядеть данные. Предположим, что входные данные должны представлять собой структуру (например, массив), содержащий элементы `processRefund` типа bool и `refundAmount` типа int.

```php
use Nette\Schema\Expect;

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

Мы считаем, что определение схемы выглядит понятным, даже если вы видите его в первый раз.

Отправим следующие данные для проверки:

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

$normalized = $processor->process($schema, $data); // OK, проходит
```

Выход, т. е. значением `$normalized`, является объект `stdClass`. Если мы хотим, чтобы результатом был массив, мы добавляем приведение к схеме `Expect::structure([...])->castTo('array')`.

Все элементы структуры являются необязательными и имеют значение по умолчанию `null`. Пример:

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

$normalized = $processor->process($schema, $data); // OK, проходит
// $normalized = {'processRefund' => null, 'refundAmount' => 17}
```

Тот факт, что значением по умолчанию является `null`, не означает, что оно будет принято во входных данных `'processRefund' => null`. Нет, входные данные должны быть булевыми, т. е. только `true` или `false`. Нам пришлось бы явно разрешить `null` через `Expect::bool()->nullable()`.

Элемент можно сделать обязательным, используя `Expect::bool()->required()`. Мы меняем значение по умолчанию на `false`, используя `Expect::bool()->default(false)` или коротко `Expect::bool(false)`.

А что если мы захотим принимать `1` и `0` помимо булевых чисел? Перечислим допустимые значения, которые мы также нормализуем в 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
```

Теперь вы знаете основы того, как определяется схема и как ведут себя отдельные элементы структуры. Теперь мы покажем, какие ещё элементы могут быть использованы при определении схемы.


Типы данных: type()
-------------------

Все стандартные типы данных PHP могут быть перечислены в схеме:

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

А затем все типы, [поддерживаемые валидаторами |utils:validators#Expected-Types] через `Expect::type('scalar')` или сокращенно `Expect::scalar()`. Также принимаются имена классов или интерфейсов, например: `Expect::type('AddressEntity')`.

Вы также можете использовать нотацию объединения:

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

Значение по умолчанию всегда `null`, за исключением `array` и `list`, где это пустой массив. (Список — это массив, индексированный в порядке возрастания числовых ключей от нуля, то есть неассоциативный массив).


Массив значений: arrayOf() listOf()
-----------------------------------

Массив — слишком общая структура, полезнее указать, какие именно элементы он может содержать. Например, массив, элементами которого могут быть только строки:

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

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
$processor->process($schema, ['key' => 123]); // ОШИБКА: 123 не строка
```

Второй параметр может использоваться для указания ключей (начиная с версии 1.2):

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

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // ОШИБКА: 'a' не int
```

Список представляет собой индексированный массив:

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

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 123]); // ОШИБКА: 123 не строка
$processor->process($schema, ['key' => 'a']); // ОШИБКА: не список
$processor->process($schema, [1 => 'a', 0 => 'b']); // ОШИБКА: не список
```

Параметр также может быть схемой, поэтому мы можем написать:

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

Значение по умолчанию — пустой массив. Если вы укажете значение по умолчанию, оно будет объединено с переданными данными. Это можно отключить с помощью `mergeDefaults(false)` (начиная с версии 1.1).


Перечисление: anyOf()
---------------------

`anyOf()` — это набор значений или схем, которыми может быть значение. Вот как записать массив элементов, которые могут быть либо `'a'`, либо `true`, либо `null`:

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

$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // ОШИБКА: false тут не место
```

Элементы перечисления также могут быть схемами:

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

$processor->process($schema, ['foo', true, null, 'bar']); // OK
$processor->process($schema, [123]); // ОШИБКА
```

Метод `anyOf()` принимает варианты как отдельные параметры, а не как массив. Чтобы передать ему массив значений, используйте оператор распаковки `anyOf(...$variants)`.

Значение по умолчанию — `null`. Используйте метод `firstIsDefault()`, чтобы сделать первый элемент элементом по умолчанию:

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


Структуры
---------

Структуры — это объекты с определенными ключами. Каждая из этих пар ключ => значение называется "свойством":

Структуры принимают массивы и объекты и возвращают объекты `stdClass` (если вы не измените его с помощью `castTo('array')` и т. д.).

По умолчанию все свойства являются необязательными и имеют значение по умолчанию `null`. Вы можете определить обязательные свойства, используя `required()`:

```php
$schema = Expect::structure([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // значение по умолчанию — null
]);

$processor->process($schema, ['optional' => '']);
// ОШИБКА: опция 'required' отсутствует

$processor->process($schema, ['required' => 'foo']);
// OK, возвращает {'required' => 'foo', 'optional' => null}
```

Если вы не хотите выводить свойства только со значением по умолчанию, используйте `skipDefaults()`:

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

$processor->process($schema, ['required' => 'foo']);
// OK, возвращает {'required' => 'foo'}
```

Хотя `null` является значением по умолчанию свойства `optional`, оно не допускается во входных данных (значение должно быть строкой). Свойства, принимающие значение `null`, определяются с помощью `nullable()`:

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

$processor->process($schema, ['optional' => null]);
// ОШИБКА: 'optional' ожидается как строка, а предоставляется null.

$processor->process($schema, ['nullable' => null]);
// OK, возвращает {'optional' => null, 'nullable' => null}
```

По умолчанию, во входных данных не может быть лишних элементов:

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

$processor->process($schema, ['additional' => 1]);
// ОШИБКА: Неожиданный элемент 'additional'
```

Подобные элементы изменить с помощью `otherItems()`. В качестве параметра мы укажем схему для каждого дополнительного элемента:

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

$processor->process($schema, ['additional' => 1]); // OK
$processor->process($schema, ['additional' => true]); // ОШИБКА
```


Устаревшие элементы
-------------------

Вы можете объявить свойство устаревшим, используя метод `deprecated([string $message])`. Уведомления об устаревании возвращаются с помощью `$processor->getWarnings()`:

```php
$schema = Expect::structure([
	'old' => Expect::int()->deprecated('Элемент %path% устарел'),
]);

$processor->process($schema, ['old' => 1]); // OK
$processor->getWarnings(); // ["Элемент 'old' устарел"]
```


Диапазоны: min() max()
----------------------

Используйте `min()` и `max()` для ограничения количества элементов в массивах:

```php
// массив, минимум 10 элементов, максимум 20 элементов
Expect::array()->min(10)->max(20);
```

Для строк ограничивает их длину:

```php
// строка, длиной не менее 10 символов, максимум 20 символов
Expect::string()->min(10)->max(20);
```

Для чисел ограничивает их значение:

```php
// целое число, от 10 до 20 включительно
Expect::int()->min(10)->max(20);
```

Конечно, можно упомянуть только `min()`, или только `max()`:

```php
// строка, максимум 20 символов
Expect::string()->max(20);
```


Регулярные выражения: pattern()
-------------------------------

Используя `pattern()`, вы можете указать регулярное выражение, которому должна соответствовать **вся** входная строка (т.е. как если бы она была завернута в символы `^` a `$`):

```php
// только 9 цифр
Expect::string()->pattern('\d{9}');
```


Пользовательские утверждения: assert()
--------------------------------------

Вы можете добавить любые другие ограничения, используя `assert(callable $fn)`.

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

$schema = Expect::arrayOf('string')
	->assert($countIsEven); // число должно быть чётным

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 'b', 'c']); // ОШИБКА: 3 - нечётное число
```

Или

```php
Expect::string()->assert('is_file'); // файл должен существовать
```

Вы можете добавить собственное описание для каждого утверждения. Оно будет частью сообщения об ошибке.

```php
$schema = Expect::arrayOf('string')
	->assert($countIsEven, 'Четные элементы в массиве');

$processor->process($schema, ['a', 'b', 'c']);
// Неудачное утверждение "Четные элементы в массиве" для элемента с массивом значений.
```

Этот метод можно вызывать многократно для добавления нескольких ограничений. Его можно смешивать с вызовами `transform()` и `castTo()`.


Трансформация: transform() .[#toc-transformation-transform]
-----------------------------------------------------------

Успешно подтвержденные данные могут быть изменены с помощью пользовательской функции:

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

Этот метод можно вызывать многократно для добавления нескольких преобразований. Он может перемежаться с вызовами `assert()` и `castTo()`. Операции будут выполняться в том порядке, в котором они объявлены:

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

Метод `transform()` может одновременно преобразовывать и проверять значение. Это часто проще и менее избыточно, чем выстраивание цепочек `transform()` и `assert()`. Для этого функция получает объект [Context |api:Nette\Schema\Context] с методом `addError()`, который может быть использован для добавления информации о проблемах валидации:

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


Кастинг: castTo() .[#toc-casting-castto]
----------------------------------------

Успешно проверенные данные могут быть приведены:

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

Помимо собственных типов PHP, можно также приводить данные к классам. При этом различается, является ли это простой класс без конструктора или класс с конструктором. Если класс не имеет конструктора, то создается его экземпляр и в его свойства записываются все элементы структуры:

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

Если класс имеет конструктор, то элементы структуры передаются конструктору в качестве именованных параметров:

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

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

Кастинг в сочетании со скалярным параметром создает объект и передает его значение в качестве единственного параметра конструктору:

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


Нормализация: before()
----------------------

Перед самой проверкой данные могут быть нормализованы с помощью метода `before()`. В качестве примера, пусть есть элемент, который должен быть массивом строк (например, `['a', 'b', 'c']`), но получает входные данные в виде строки `a b c`:

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

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

$normalized = $processor->process($schema, 'a b c');
// OK, возвращает ['a', 'b', 'c']
```


Отображение на объекты: from() .[#toc-mapping-to-objects-from]
--------------------------------------------------------------

Из класса можно сгенерировать структурную схему. Пример:

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

Поддерживаются также анонимные классы:

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

Поскольку информации, полученной из определения класса, может быть недостаточно, можно добавить пользовательскую схему для элементов с помощью второго параметра:

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

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

Schema: валидация данных

Практичная библиотека для проверки и нормализации структур данных по заданной схеме с интеллектуальным и простым в понимании API.

Установка:

composer require nette/schema

Использование

В переменной $schema у нас есть схема валидации (что именно это значит и как её создать, мы расскажем позже), а в переменной $data у нас есть структура данных, которую мы хотим валидировать и нормализовать. Это могут быть, например, данные, отправленные пользователем через API, конфигурационный файл и т. д.

Задачей занимается класс Nette\Schema\Processor, который обрабатывает входные данные и либо возвращает нормализованные данные, либо выбрасывает исключение Nette\Schema\ValidationException при ошибке.

$processor = new Nette\Schema\Processor;

try {
	$normalized = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
	echo 'Data is invalid: ' . $e->getMessage();
}

Метод $e->getMessages() возвращает массив всех строк сообщений, а $e->getMessageObjects() возвращает все сообщения в виде объектов Nette\Schema\Message.

Определение схемы

А теперь давайте создадим схему. С помощью класса Nette\Schema\Expect мы фактически определяем, как должны выглядеть данные. Предположим, что входные данные должны представлять собой структуру (например, массив), содержащий элементы processRefund типа bool и refundAmount типа int.

use Nette\Schema\Expect;

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

Мы считаем, что определение схемы выглядит понятным, даже если вы видите его в первый раз.

Отправим следующие данные для проверки:

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

$normalized = $processor->process($schema, $data); // OK, проходит

Выход, т. е. значением $normalized, является объект stdClass. Если мы хотим, чтобы результатом был массив, мы добавляем приведение к схеме Expect::structure([...])->castTo('array').

Все элементы структуры являются необязательными и имеют значение по умолчанию null. Пример:

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

$normalized = $processor->process($schema, $data); // OK, проходит
// $normalized = {'processRefund' => null, 'refundAmount' => 17}

Тот факт, что значением по умолчанию является null, не означает, что оно будет принято во входных данных 'processRefund' => null. Нет, входные данные должны быть булевыми, т. е. только true или false. Нам пришлось бы явно разрешить null через Expect::bool()->nullable().

Элемент можно сделать обязательным, используя Expect::bool()->required(). Мы меняем значение по умолчанию на false, используя Expect::bool()->default(false) или коротко Expect::bool(false).

А что если мы захотим принимать 1 и 0 помимо булевых чисел? Перечислим допустимые значения, которые мы также нормализуем в 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

Теперь вы знаете основы того, как определяется схема и как ведут себя отдельные элементы структуры. Теперь мы покажем, какие ещё элементы могут быть использованы при определении схемы.

Типы данных: type()

Все стандартные типы данных PHP могут быть перечислены в схеме:

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

А затем все типы, поддерживаемые валидаторами через Expect::type('scalar') или сокращенно Expect::scalar(). Также принимаются имена классов или интерфейсов, например: Expect::type('AddressEntity').

Вы также можете использовать нотацию объединения:

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

Значение по умолчанию всегда null, за исключением array и list, где это пустой массив. (Список — это массив, индексированный в порядке возрастания числовых ключей от нуля, то есть неассоциативный массив).

Массив значений: arrayOf() listOf()

Массив — слишком общая структура, полезнее указать, какие именно элементы он может содержать. Например, массив, элементами которого могут быть только строки:

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

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
$processor->process($schema, ['key' => 123]); // ОШИБКА: 123 не строка

Второй параметр может использоваться для указания ключей (начиная с версии 1.2):

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

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // ОШИБКА: 'a' не int

Список представляет собой индексированный массив:

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

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 123]); // ОШИБКА: 123 не строка
$processor->process($schema, ['key' => 'a']); // ОШИБКА: не список
$processor->process($schema, [1 => 'a', 0 => 'b']); // ОШИБКА: не список

Параметр также может быть схемой, поэтому мы можем написать:

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

Значение по умолчанию — пустой массив. Если вы укажете значение по умолчанию, оно будет объединено с переданными данными. Это можно отключить с помощью mergeDefaults(false) (начиная с версии 1.1).

Перечисление: anyOf()

anyOf() — это набор значений или схем, которыми может быть значение. Вот как записать массив элементов, которые могут быть либо 'a', либо true, либо null:

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

$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // ОШИБКА: false тут не место

Элементы перечисления также могут быть схемами:

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

$processor->process($schema, ['foo', true, null, 'bar']); // OK
$processor->process($schema, [123]); // ОШИБКА

Метод anyOf() принимает варианты как отдельные параметры, а не как массив. Чтобы передать ему массив значений, используйте оператор распаковки anyOf(...$variants).

Значение по умолчанию — null. Используйте метод firstIsDefault(), чтобы сделать первый элемент элементом по умолчанию:

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

Структуры

Структуры — это объекты с определенными ключами. Каждая из этих пар ключ ⇒ значение называется „свойством“:

Структуры принимают массивы и объекты и возвращают объекты stdClass (если вы не измените его с помощью castTo('array') и т. д.).

По умолчанию все свойства являются необязательными и имеют значение по умолчанию null. Вы можете определить обязательные свойства, используя required():

$schema = Expect::structure([
	'required' => Expect::string()->required(),
	'optional' => Expect::string(), // значение по умолчанию — null
]);

$processor->process($schema, ['optional' => '']);
// ОШИБКА: опция 'required' отсутствует

$processor->process($schema, ['required' => 'foo']);
// OK, возвращает {'required' => 'foo', 'optional' => null}

Если вы не хотите выводить свойства только со значением по умолчанию, используйте skipDefaults():

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

$processor->process($schema, ['required' => 'foo']);
// OK, возвращает {'required' => 'foo'}

Хотя null является значением по умолчанию свойства optional, оно не допускается во входных данных (значение должно быть строкой). Свойства, принимающие значение null, определяются с помощью nullable():

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

$processor->process($schema, ['optional' => null]);
// ОШИБКА: 'optional' ожидается как строка, а предоставляется null.

$processor->process($schema, ['nullable' => null]);
// OK, возвращает {'optional' => null, 'nullable' => null}

По умолчанию, во входных данных не может быть лишних элементов:

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

$processor->process($schema, ['additional' => 1]);
// ОШИБКА: Неожиданный элемент 'additional'

Подобные элементы изменить с помощью otherItems(). В качестве параметра мы укажем схему для каждого дополнительного элемента:

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

$processor->process($schema, ['additional' => 1]); // OK
$processor->process($schema, ['additional' => true]); // ОШИБКА

Устаревшие элементы

Вы можете объявить свойство устаревшим, используя метод deprecated([string $message]). Уведомления об устаревании возвращаются с помощью $processor->getWarnings():

$schema = Expect::structure([
	'old' => Expect::int()->deprecated('Элемент %path% устарел'),
]);

$processor->process($schema, ['old' => 1]); // OK
$processor->getWarnings(); // ["Элемент 'old' устарел"]

Диапазоны: min() max()

Используйте min() и max() для ограничения количества элементов в массивах:

// массив, минимум 10 элементов, максимум 20 элементов
Expect::array()->min(10)->max(20);

Для строк ограничивает их длину:

// строка, длиной не менее 10 символов, максимум 20 символов
Expect::string()->min(10)->max(20);

Для чисел ограничивает их значение:

// целое число, от 10 до 20 включительно
Expect::int()->min(10)->max(20);

Конечно, можно упомянуть только min(), или только max():

// строка, максимум 20 символов
Expect::string()->max(20);

Регулярные выражения: pattern()

Используя pattern(), вы можете указать регулярное выражение, которому должна соответствовать вся входная строка (т.е. как если бы она была завернута в символы ^ a $):

// только 9 цифр
Expect::string()->pattern('\d{9}');

Пользовательские утверждения: assert()

Вы можете добавить любые другие ограничения, используя assert(callable $fn).

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

$schema = Expect::arrayOf('string')
	->assert($countIsEven); // число должно быть чётным

$processor->process($schema, ['a', 'b']); // OK
$processor->process($schema, ['a', 'b', 'c']); // ОШИБКА: 3 - нечётное число

Или

Expect::string()->assert('is_file'); // файл должен существовать

Вы можете добавить собственное описание для каждого утверждения. Оно будет частью сообщения об ошибке.

$schema = Expect::arrayOf('string')
	->assert($countIsEven, 'Четные элементы в массиве');

$processor->process($schema, ['a', 'b', 'c']);
// Неудачное утверждение "Четные элементы в массиве" для элемента с массивом значений.

Этот метод можно вызывать многократно для добавления нескольких ограничений. Его можно смешивать с вызовами transform() и castTo().

Трансформация: transform()

Успешно подтвержденные данные могут быть изменены с помощью пользовательской функции:

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

Этот метод можно вызывать многократно для добавления нескольких преобразований. Он может перемежаться с вызовами assert() и castTo(). Операции будут выполняться в том порядке, в котором они объявлены:

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

Метод transform() может одновременно преобразовывать и проверять значение. Это часто проще и менее избыточно, чем выстраивание цепочек transform() и assert(). Для этого функция получает объект Context с методом addError(), который может быть использован для добавления информации о проблемах валидации:

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);
	});

Кастинг: castTo()

Успешно проверенные данные могут быть приведены:

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

Помимо собственных типов 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

Если класс имеет конструктор, то элементы структуры передаются конструктору в качестве именованных параметров:

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

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

Кастинг в сочетании со скалярным параметром создает объект и передает его значение в качестве единственного параметра конструктору:

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

Нормализация: before()

Перед самой проверкой данные могут быть нормализованы с помощью метода before(). В качестве примера, пусть есть элемент, который должен быть массивом строк (например, ['a', 'b', 'c']), но получает входные данные в виде строки a b c:

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

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

$normalized = $processor->process($schema, 'a b c');
// OK, возвращает ['a', 'b', 'c']

Отображение на объекты: from()

Из класса можно сгенерировать структурную схему. Пример:

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}

Поддерживаются также анонимные классы:

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

Поскольку информации, полученной из определения класса, может быть недостаточно, можно добавить пользовательскую схему для элементов с помощью второго параметра:

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