nette/schema
nette/schema is a small PHP library for validating and normalizing structured data. Define schemas for arrays, configuration, and user input with type checks, defaults, required fields, ranges, and clear error messages—ideal for safe config loading.
Install via Composer:
composer require nette/schema
Start by validating simple configuration arrays — e.g., reading config from config.php or environment variables. Define schemas with Nette\Schema\Expect, then validate with Nette\Schema\Processor:
use Nette\Schema\Expect;
use Nette\Schema\Processor;
$schema = Expect::structure([
'app_name' => Expect::string()->required(),
'debug' => Expect::bool()->default(false),
]);
$processor = new Processor();
$config = $processor->process($schema, $rawConfig);
First use case: validating application config (e.g., after loading from a .env-parsed array or JSON file). Check the README and API docs for quick reference — focus on Expect::structure(), Expect::string(), and .required().
Use in Laravel bootstrapping (e.g., in config/app.php or a custom ConfigLoader) to ensure config integrity before service resolution:
// In a service provider or bootstrap script
$schema = Expect::structure([
'log' => Expect::structure([
'level' => Expect::oneOf(['debug', 'info', 'error'])->default('info'),
'path' => Expect::string()->required(),
]),
]);
$raw = config('app.log'); // Or from file
$validated = $processor->process($schema, $raw);
config()->set('app.log', $validated); // Normalize and merge
Parse and validate incoming requests (e.g., JSON payloads for API endpoints) using schemas before domain logic:
$requestData = json_decode($request->getContent(), true);
$schema = Expect::structure([
'email' => Expect::email()->required(),
'tags' => Expect::listOf(Expect::string())->minLength(1),
'metadata' => Expect::arrayOf(Expect::string(), Expect::string()),
]);
$data = $processor->process($schema, $requestData);
// $data is now strongly-typed and safe for use
castTo()Define reusable schemas and map validated data to DTOs/Models:
$addressSchema = Expect::structure([
'street' => Expect::string()->required(),
'city' => Expect::string()->required(),
])->castTo(Address::class);
$userSchema = Expect::structure([
'name' => Expect::string()->required(),
'address' => $addressSchema,
]);
merge() & extend()Build layered configs (e.g., base → dev → production):
$base = Expect::structure(['timeout' => 30]);
$env = Expect::structure(['timeout' => 5, 'debug' => true])->merge($base);
// Or use extend() for shallow merging of keys
$dev = Expect::structure(['debug' => true])->extend($base);
Expect::array() ≠ associative array only: Passing a numerically indexed array ([1,2,3]) to Expect::array() strips keys → silent data loss. Use Expect::listOf() for lists.
Defaults only apply on missing or null keys: If a key exists but is null, default() won’t override it unless explicitly marked nullable() first.
AnyOf() must have ≥1 item: AnyOf([]) throws SchemaException. Guard dynamic schema generation (e.g., check if ($conditions) before building branches).
Path context is dot-notation: Errors like database.host help debugging. Override Processor::onError to enrich error messages (e.g., localize for frontend).
transform() is pure and side-effect-free: Only use it for coercive normalization (e.g., 'TRUE' → true, or string → enum). Avoid logging, DB calls, or mutation.
PHP 8.1+ required: Uses typed properties and readonly classes — no fallback. Conflicts with legacy Laravel setups (≤Laravel 8/PHP 8.0).
Error codes enable precision: ValidationException::getMessages() returns code (e.g., 'type.string', 'required'). Map codes to UI-friendly messages:
$messages = $e->getMessages();
foreach ($messages as $msg) {
$human = match ($msg->code) {
'required' => 'This field is required.',
'type.string' => 'Must be a string.',
default => $msg->message,
};
}
Structure::skipDefaults() cleans output: Use when caching or diffing configs — avoids bloating output with default values.
Static analysis: PHPStan level 9 — run with phpstan.neon to catch schema misuses early (e.g., non-existent keys, type mismatches).
How can I help you explore Laravel packages today?