plugins/nette/skills/nette-schema/SKILL.md
Provides Nette Schema for data validation and normalization. Use when validating configuration, API inputs, or any data structures with Expect class. Covers Expect::structure(), Expect::from(), anyOf, arrayOf, listOf, assert, transform, castTo, otherItems, and Processor. This is about nette/schema – not Nette Forms validation (addRule), not Nette\Utils\Validators, and not JSON Schema.
npx skillsauth add nette/claude-code nette-schemaInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A library for validating and normalizing data structures against a defined schema. Declare what correct data looks like, and Schema verifies and transforms input in one step.
composer require nette/schema
loadConfiguration()Schema is complementary to Nette Forms (which handles user input with UI) and database constraints (which enforce storage-level rules). Use Schema for programmatic data validation where there's no form UI.
use Nette\Schema\Expect;
use Nette\Schema\Processor;
$schema = Expect::structure([
'name' => Expect::string()->required(),
'email' => Expect::email(),
'age' => Expect::int()->min(0)->max(120),
]);
$processor = new Processor;
try {
$normalized = $processor->process($schema, $data);
// $normalized is stdClass with validated data
} catch (Nette\Schema\ValidationException $e) {
echo 'Invalid: ' . $e->getMessage();
// $e->getMessages() returns array of all errors
}
Expect::string() // string, default null
Expect::string('default') // string with default value
Expect::int() // integer
Expect::float() // float
Expect::bool() // boolean
Expect::null() // null only
Expect::array() // array, default []
Expect::scalar() // scalar value
Expect::type('ClassName') // instance of class
Expect::type('bool|string') // union types
// Array of strings
Expect::arrayOf('string')
Expect::arrayOf(Expect::string())
// Array with string keys
Expect::arrayOf('string', 'string')
// List (indexed array)
Expect::listOf('string')
// Tuple (fixed positions)
Expect::array([
Expect::int(),
Expect::string(),
Expect::bool(),
])
$schema = Expect::structure([
'database' => Expect::structure([
'host' => Expect::string()->required(),
'port' => Expect::int(3306),
'user' => Expect::string()->required(),
'password' => Expect::string()->nullable(),
]),
'debug' => Expect::bool(false),
]);
Properties are optional by default (null). Use required() for mandatory fields.
// One of specific values
Expect::anyOf('small', 'medium', 'large')
// One of values or schemas
Expect::anyOf(
Expect::string(),
Expect::int(),
null
)
// First is default
Expect::anyOf('small', 'medium', 'large')->firstIsDefault()
// Required field
Expect::string()->required()
// Nullable (accepts null)
Expect::string()->nullable()
// Default value
Expect::string()->default('hello')
Expect::string('hello') // shorthand
// Length/count limits
Expect::string()->min(3)->max(100)
Expect::array()->min(1)->max(10)
// Numeric range
Expect::int()->min(0)->max(100)
// Pattern
Expect::string()->pattern('\d{5}') // regex for entire value
// Custom validation
Expect::string()->assert(fn($s) => strlen($s) % 2 === 0, 'Must be even length')
// Built-in validators
Expect::string()->assert('is_file')
Expect::string()->assert('ctype_alpha')
// Transform value after validation
Expect::string()->transform(fn($s) => strtoupper($s))
// Chain transformations
Expect::string()
->assert('ctype_lower', 'Must be lowercase')
->transform(fn($s) => strtoupper($s))
// Transform with validation
Expect::string()->transform(function ($s, $context) {
if (!ctype_alpha($s)) {
$context->addError('Must be letters only');
return null;
}
return strtoupper($s);
})
// Cast to type
Expect::scalar()->castTo('string')
Expect::scalar()->castTo('int')
Expect::scalar()->castTo('bool')
// Cast to class (without constructor)
Expect::structure([
'name' => Expect::string(),
'age' => Expect::int(),
])->castTo(Person::class)
// Cast to class with constructor
Expect::structure([
'host' => Expect::string(),
'port' => Expect::int(),
])->castTo(DatabaseConfig::class)
// Creates: new DatabaseConfig(host: ..., port: ...)
// Normalize before validation
Expect::arrayOf('string')
->before(fn($v) => is_string($v) ? explode(' ', $v) : $v)
// Now accepts both:
// - ['a', 'b', 'c']
// - 'a b c' (converted to array)
// Allow extra items
Expect::structure([
'known' => Expect::string(),
])->otherItems(Expect::mixed())
// Skip default values in output
Expect::structure([
'debug' => Expect::bool(false),
])->skipDefaults()
// Extend structure
$base = Expect::structure(['name' => Expect::string()]);
$extended = $base->extend(['email' => Expect::email()]);
Generate schema from class properties:
class Config
{
public string $name;
public ?string $email = null;
public bool $debug = false;
}
$schema = Expect::from(new Config);
// Override specific fields
$schema = Expect::from(new Config, [
'email' => Expect::email()->required(),
]);
$schema = Expect::structure([
'oldOption' => Expect::int()->deprecated('Use newOption instead'),
'newOption' => Expect::int(),
]);
$processor->process($schema, $data);
$warnings = $processor->getWarnings();
Configuration validation:
$configSchema = Expect::structure([
'database' => Expect::structure([
'driver' => Expect::anyOf('mysql', 'pgsql', 'sqlite')->required(),
'host' => Expect::string('localhost'),
'port' => Expect::int(),
'name' => Expect::string()->required(),
'user' => Expect::string()->required(),
'password' => Expect::string()->nullable(),
])->castTo('array'),
'cache' => Expect::structure([
'enabled' => Expect::bool(true),
'ttl' => Expect::int(3600)->min(0),
]),
'mail' => Expect::structure([
'from' => Expect::email()->required(),
'smtp' => Expect::structure([
'host' => Expect::string(),
'port' => Expect::int(587),
'secure' => Expect::anyOf('tls', 'ssl', null),
]),
]),
]);
API input validation:
$createUserSchema = Expect::structure([
'username' => Expect::string()
->required()
->min(3)->max(20)
->pattern('[a-z0-9_]+'),
'email' => Expect::email()->required(),
'password' => Expect::string()->required()->min(8),
'roles' => Expect::listOf(
Expect::anyOf('user', 'admin', 'moderator')
)->default(['user']),
])->castTo('array');
For detailed information, use WebFetch on these URLs:
tools
CRITICAL: Read BEFORE writing or modifying any PHP file. A PostToolUse hook automatically runs nette/coding-standard (ECS) on every PHP file after each Edit or Write. The fixer removes unused `use` statements - so never add `use` statements in a separate edit before the code that references them. Always include `use` imports in the same edit as the referencing code, or add the code first then `use` statements. This skill should be used whenever creating new PHP files, editing existing PHP code, adding methods, refactoring, or fixing bugs in PHP - even for small one-line changes.
development
Install nette/coding-standard globally for PHP code style checking
tools
Invoke when fetching web pages from localhost, debugging PHP errors, or interpreting Tracy output (BlueScreen, Tracy Bar, dump). Read BEFORE running curl or Chrome to any local development PHP URL – with Tracy >= 2.12 and a detected agent, Tracy mirrors BlueScreen, Tracy Bar and dumps as markdown into the JS console for easy machine reading. For Chrome MCP, call list_console_messages() to read Tracy output. Essential when: 500 error, blank page, PHP exception, slow page, N+1 queries, or inspecting variables with dump().
tools
Provides Nette Utils helper classes. Use when working with Arrays, Strings, Image, Finder, FileSystem, Json, Validators, DateTime, Html element builder, Random, Callback, Type, or SmartObject from nette/utils. Do NOT use for Nette Schema, Nette Forms, Nette Database, Latte filters, or DI configuration.