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

version: 2.x 1.x