Nette Schema
Does your application read a configuration file, or accept data from an API? Before your code touches such data, it should check that the data has the shape you expect. Nette Schema does it for you:
- You describe the expected data in a few lines of readable PHP code
- It reports every mistake at once, with the path to the item
- It fills in default values, converts values and maps them to your objects
- It merges configuration from several files according to the schema
- It exports the schema to JSON Schema, so editors can offer completion
Installation
Download and install the package using Composer:
composer require nette/schema
Your First Schema
The easiest way to show it is with an example. Let's say your application connects to a database and reads the connection settings from a configuration file. Once loaded, the configuration is an ordinary PHP array, for example:
$data = [
'user' => 'admin',
'port' => 5432,
];
Nothing guarantees that the file contains what your code needs. Someone may misspell a key, write the port as text or forget the user name altogether. So we describe what the data should look like. This description is the schema, and the class Nette\Schema\Expect builds it:
use Nette\Schema\Expect;
$schema = Expect::structure([
'host' => Expect::string('localhost'),
'port' => Expect::int(3306),
'user' => Expect::string()->required(),
'password' => Expect::string()->nullable(),
]);
It reads almost like a sentence. The data is a structure with four items: host is a string, localhost
when not given; port is an integer, 3306 by default; user is a string that must be given; and
password is a string that may also be null.
Now we let the Nette\Schema\Processor check the data against the schema:
$processor = new Nette\Schema\Processor;
$config = $processor->process($schema, $data);
echo $config->host; // 'localhost'
echo $config->port; // 5432
The result is not the same array. It is a stdClass object with all four items: the missing host got
its default value, and the missing password is null. Your code can rely on every item being there and
having the right type.
When the Data Is Wrong
And what if the configuration contains a mistake? Let's say the port is written as text and the user is missing:
$data = [
'port' => 'abc',
];
try {
$config = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
echo implode("\n", $e->getMessages());
}
The item 'port' expects to be int, 'abc' given.
The mandatory item 'user' is missing.
Notice that you get all the mistakes at once, not just the first one, and each message says which item it concerns. The user fixes the whole file in one go instead of running the application again and again.
The method getMessages() returns the messages as strings. When you want to show them in your own way,
getMessageObjects() returns objects that carry the path to the item and a code of the error as well. More on this in
Validation and Normalization.
Describing the Data
A real configuration has more than four items. Let's put the database connection into a nested structure and add a list of administrators' e-mails, the mode the application runs in, and the date it goes live:
enum Mode: string
{
case Development = 'development';
case Production = 'production';
}
$schema = Expect::structure([
'database' => Expect::structure([
'host' => Expect::string('localhost'),
'port' => Expect::int(3306)->min(1)->max(65535),
'user' => Expect::string()->required(),
]),
'admins' => Expect::listOf('email'),
'mode' => Expect::enum(Mode::class)->default(Mode::Production),
'launchDate' => Expect::string()->nullable()->castTo(DateTimeImmutable::class),
]);
Each item says exactly what it accepts. The port must be between 1 and 65535. admins is a list in which every
item must be a valid e-mail address. mode accepts the string 'development' or 'production'
from the file, and your code gets a case of the enum Mode. launchDate turns a string like
'2026-03-01' into a DateTimeImmutable object.
All the data types and their options, from numbers and strings to structures and tuples, are described on the page Data Types.
Converting the Data
Validation is just one half. The other is turning the data into what your code works with best. Instead of a
stdClass, you will probably want your own class. Expect::from() creates the schema straight from it,
using the types of its properties:
class DatabaseConfig
{
public string $host = 'localhost';
public int $port = 3306;
public string $user;
public ?string $password = null;
}
$schema = Expect::from(new DatabaseConfig);
$config = $processor->process($schema, ['user' => 'admin']); // an instance of DatabaseConfig
Besides that, you can adjust a value before it is validated with before(), change it afterwards with
transform(), cast it to another type with castTo() and add your own checks with assert().
All of that is on the page Validation and Normalization.
Configuration in Layers
Applications often assemble their configuration from several sources: built-in defaults, a shared configuration file and a
local file of each developer, where each one overrides only the items it mentions. The method processMultiple()
merges them in order and validates the result as a whole:
$defaults = ['host' => 'localhost', 'port' => 3306];
$local = ['user' => 'admin', 'port' => 5432];
$config = $processor->processMultiple($schema, [$defaults, $local]);
The schema decides how the layers are combined, for example the items of a list are appended one after another. How that works is described in Merging Configurations.
JSON Schema for Your Editor
Your schema is already a precise description of what the configuration may contain.
Nette\Schema\JsonSchema::export() turns it into a JSON Schema document, which editors read to offer completion and
underline mistakes right in the configuration file. See Exporting to JSON
Schema.
Nette Schema in Nette Applications
You use the library even when you don't call it yourself: every extension of the DI container describes its configuration section with a schema in the method getConfigSchema(). That is why a typo in the configuration of your application is reported with a clear message.
What's Next
- Data Types – every element of a schema and its options
- Validation and Normalization – custom checks, transformations, casting and mapping to objects
- Merging Configurations – combining configuration layers
- Exporting to JSON Schema – completion and checking of configuration in editors
- Custom Elements – how processing works and how to write your own element