Data Types
A schema is made of elements, each describing one piece of the data. This page goes through all of them:
- simple types, formats and ranges
- default values, required and nullable items
- arrays, lists, enumerations and enums
- structures and tuples
- descriptions and deprecated items
The examples continue the configuration of an application from the overview.
$processor is an instance of Nette\Schema\Processor.
Simple Types: type()
All standard PHP data types can be specified in the schema:
Expect::string($default = null);
Expect::int($default = null);
Expect::float($default = null);
Expect::bool($default = null);
Expect::null();
Expect::array($default = []);
Expect::list($default = []);
And also all types supported by the Validators
class, for example Expect::type('scalar') or shorthand Expect::scalar(). Also class or interface
names, e.g., Expect::type('AddressEntity').
Union syntax can also be used:
Expect::type('bool|string|array')
The default value is always null with the exception of array and list, where it is an
empty array. (A list is an array indexed by a sequence of numeric keys starting from zero, i.e. a non-associative array).
In version 1.x, the type '?int[]' means an array whose items are integers or null. Version 2.0 reads
it the way phpDoc does, as an array of integers, or null in place of the whole array. If you mean an array whose
items may be null, write Expect::arrayOf(Expect::int()->nullable()), which means the same in both
versions.
Default, Required and Nullable
Every item that is missing in the input gets its default value. For most types it is null, and you set another one
as the parameter or with default():
Expect::int(3306);
Expect::int()->default(3306); // the same
A default value of null does not mean that the input may contain null. Expect::string()
accepts only a string, so 'password' => null in the input is an error. To allow null, call
nullable():
$schema = Expect::structure([
'user' => Expect::string(),
'password' => Expect::string()->nullable(),
]);
$processor->process($schema, ['user' => null]);
// ERROR: The item 'user' expects to be string, null given.
$processor->process($schema, ['password' => null]);
// OK, returns {'password' => null, 'user' => null}
An item that must be given is marked with required(). When it is missing, the processing reports an error instead
of using the default value:
$schema = Expect::structure([
'user' => Expect::string()->required(),
]);
$processor->process($schema, []);
// ERROR: The mandatory item 'user' is missing.
Ranges: min() max()
Use min() and max() to limit the count for arrays:
// array, at least 1 item, maximum 5 items
Expect::listOf('email')->min(1)->max(5);
For strings, they limit the length:
// string, at least 8 bytes long, maximum 64 bytes
Expect::string()->min(8)->max(64);
The length of a string is measured in bytes. To count characters of a UTF-8 string, use Expect::unicode().
For numbers, they limit the value:
// integer, between 1 and 65535 inclusive
Expect::int()->min(1)->max(65535);
Of course, it is possible to specify just min() or just max():
// string, maximum 64 bytes
Expect::string()->max(64);
On a union such as Expect::type('int|string'), it is not clear whether min() and
max() should limit the number or the length of the string, so they raise an E_USER_DEPRECATED notice
there. Give the limits to each variant of anyOf() instead:
Expect::anyOf(Expect::int()->min(1), Expect::string()->min(1));
Regular Expressions: pattern()
Using pattern(), you can specify a regular expression that the entire input string must match (i.e. as if
it were wrapped in ^ and $ characters):
// exactly 9 digits
Expect::string()->pattern('\d{9}');
A pattern only makes sense for a string. That is why Expect::string() returns a
StringType element, while Expect::int() and Expect::float() return a
NumberType and Expect::listOf() and the other array schemas an ArrayType, each offering the
methods of its kind. Calling pattern() on a number or an array raises an E_USER_DEPRECATED notice, and
your editor flags such a call as deprecated as soon as you write it.
Arrays and Lists: arrayOf() listOf()
An array represents too general a structure; it's more useful to specify precisely which elements it may contain. For example, an array whose elements can only be strings:
$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 is not a string
The second parameter can specify keys (since version 1.2):
$schema = Expect::arrayOf('string', 'int');
$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' is not an int
A list is an indexed array:
$schema = Expect::listOf('email');
$processor->process($schema, ['jim@example.com', 'ann@example.com']); // OK
$processor->process($schema, ['jim@example.com', 'jim']); // ERROR: 'jim' is not an e-mail
$processor->process($schema, ['key' => 'jim@example.com']); // ERROR: not a list
With the wrap parameter, a single value is also accepted and wrapped into a one-item list.
A configuration with a single administrator then does not need the brackets:
$schema = Expect::listOf('email', wrap: true);
$processor->process($schema, 'jim@example.com'); // OK, gives ['jim@example.com']
The parameter can also be a schema, so we can write:
Expect::arrayOf(Expect::bool())
The default value is an empty array. If you specify a default value, it will be merged with the passed data. This can be
disabled using mergeDefaults(false) (since version 1.1).
Enumeration: anyOf()
anyOf() represents a set of values or schemas that a value can take. Here's how to write an array of elements
that can be either 'a', true, or null:
$schema = Expect::listOf(
Expect::anyOf('a', true, null),
);
$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // ERROR: false does not belong there
The elements of the enumeration can also be schemas:
$schema = Expect::listOf(
Expect::anyOf(Expect::string(), true, null),
);
$processor->process($schema, ['foo', true, null, 'bar']); // OK
$processor->process($schema, [123]); // ERROR
The anyOf() method accepts variants as separate parameters, not as an array. To pass it an array of values, use
the unpack operator anyOf(...$variants).
Variants are tried in the order they are written, and the first one that matches wins. The only exception is null:
when the set accepts it, a null input always gives null, even if a structure or array variant listed
earlier would otherwise turn it into an empty structure or array.
The default value is null. Use the firstIsDefault() method to make the first item the default:
// default is 'hello'
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();
Enums: enum()
A configuration file can only hold a plain string such as 'production', but in your code you would much rather
work with an enum. enum() converts one into the other: give it a backed enum, i.e. one whose cases have values
(enum Mode: string), and it accepts either the value or the case itself, and always gives you back the case:
enum Mode: string
{
case Development = 'development';
case Production = 'production';
}
$schema = Expect::enum(Mode::class);
$processor->process($schema, 'production'); // OK, returns Mode::Production
$processor->process($schema, Mode::Development); // OK, returns Mode::Development
$processor->process($schema, 'test'); // ERROR: expects to be Mode
Enums backed by integers work exactly the same, and so does everything you know from the other elements, such as
Expect::enum(Mode::class)->required() or ->default(Mode::Production).
When generating a schema from a
class, a public Mode $mode property gives you this very element, so you do not have to reach for
enum() there. Expect::type(Mode::class) is a different thing: like any class name, it accepts only an
instance, i.e. the case itself.
Structures
Structures are objects with defined keys. Each key-value pair is referred to as a “property”.
Structures accept arrays and objects and return stdClass objects. To get an array instead, add
castTo('array').
By default, all properties are optional and have a default value of null. You can define mandatory properties
using required():
$schema = Expect::structure([
'user' => Expect::string()->required(),
'password' => Expect::string(), // default value is null
]);
$processor->process($schema, ['password' => 'secret']);
// ERROR: The mandatory item 'user' is missing.
$processor->process($schema, ['user' => 'admin']);
// OK, returns {'user' => 'admin', 'password' => null}
A structure itself is mandatory. Therefore, if it is nested inside another structure and the input does not contain it, it is
created anyway – and it reports an error when it contains a required property. Use required(false) to make the
entire nested structure optional. If it is missing in the input, null appears in the output, but if it is present,
its required properties are enforced:
$schema = Expect::structure([
'database' => Expect::structure([
'user' => Expect::string()->required(),
])->required(false),
]);
$processor->process($schema, []);
// OK, returns {'database' => null}
$processor->process($schema, ['database' => []]);
// ERROR: The mandatory item 'database › user' is missing.
If you do not want properties with default values in the output, use skipDefaults():
$schema = Expect::structure([
'user' => Expect::string()->required(),
'password' => Expect::string(),
])->skipDefaults();
$processor->process($schema, ['user' => 'admin']);
// OK, returns {'user' => 'admin'}
The array of all structure properties is returned by the getShape() method.
By default, no additional items can be present in the input data. That is how a misspelled key is caught:
$schema = Expect::structure([
'host' => Expect::string(),
]);
$processor->process($schema, ['hots' => 'localhost']);
// ERROR: Unexpected item 'hots', did you mean 'host'?
This can be changed using otherItems(). As a parameter, pass the schema to validate each extra item:
$schema = Expect::structure([
'host' => Expect::string(),
])->otherItems(Expect::int());
$processor->process($schema, ['timeout' => 30]); // OK
$processor->process($schema, ['timeout' => true]); // ERROR
You can create a new structure by extending another using extend():
$database = Expect::structure([
'host' => Expect::string('localhost'),
'port' => Expect::int(3306),
]);
$replica = $database->extend([
'readOnly' => Expect::bool(true),
]);
Array
An array with defined keys. Everything that applies to structures applies to it.
$schema = Expect::array([
'user' => Expect::string()->required(),
'password' => Expect::string(), // default value is null
]);
Tuples: tuple()
A tuple is a fixed-size array where each position has its own schema:
$schema = Expect::tuple([
Expect::int(),
Expect::string(),
Expect::bool(),
]);
$processor->process($schema, [1, 'hello', true]); // OK
The input must be a list, so positions given by keys or in a different order are refused. When the input is shorter, the
missing positions behave like missing items of a structure: they get their default value, or report an error when they are
required(). otherItems() lets further items follow the defined positions and sets the schema they must
match, while extend() adds more positions to the end.
When configurations are layered, a later tuple replaces the earlier one as a whole, because mixing the positions of two different tuples would only produce nonsense.
Unlike a structure, a tuple does not accept null as an empty value; call nullable() when
null is a valid value.
Deprecated Items: deprecated()
When you rename or drop a configuration option, the old one can keep working for a while and warn its users. Mark it with
deprecated([string $message]). The warnings are returned by $processor->getWarnings():
$schema = Expect::structure([
'server' => Expect::string()->deprecated('The item %path% is deprecated, use host'),
'host' => Expect::string(),
]);
$processor->process($schema, ['server' => 'localhost']); // OK
$processor->getWarnings(); // ["The item 'server' is deprecated, use host"]
Description: description()
Any element can carry a human-readable description. It has no effect on validation, but the schema is often the only place
where the meaning of a configuration option is written down at all, and description() lets you write it right there.
The description also travels into the JSON Schema export:
$schema = Expect::structure([
'host' => Expect::string('localhost')->description('Database server hostname'),
'port' => Expect::int(3306)->description('Port the server listens on'),
])->description('Database connection');