It is my fervent wish that this file guide every AI coding agent working with code in this repository.
Any distilled, agent-facing documentation for this package - how it works
internally and the rationale behind key design decisions - lives in docs/.
Consult it before non-trivial changes; it is the source of truth from which the
public manual is distilled.
Small package, one coherent but tricky mechanism (the Processor pipeline over
Elements/*). Read docs/internals.md before editing it - the "phase" model is
subtler than it looks.
Nette Schema validates and normalizes data structures (config files, API
inputs) through a fluent Expect:: builder and a Processor.
- PHP Version: 8.1 - 8.5
- Package:
nette/schema(dep:nette/utils);master= 2.0-dev, maintenance lives onv1.xbranches
# Run all tests
vendor/bin/tester tests/Schema/ -s # or: composer tester
vendor/bin/tester tests/Schema/Expect.structure.phpt -s
# Static analysis (PHPStan level 8)
composer phpstan- Every file starts with
declare(strict_types=1);; tabs; single quotes;@internalfor implementation details,@methodforExpect's magic methods; deprecations use the native#[\Deprecated]attribute, not phpDoc; Nette Coding Standard. - Tests are Nette Tester
.phptnamedExpect.<feature>.phpt;checkValidationErrors()asserts the expected error messages of a failingprocess().
- There is no
validate()phase. TheSchemainterface has four operations butProcessorruns only two per call:process()=normalize()+complete();processMultiple()=normalize()each item,merge()left-to-right, onecomplete(). Validation happens insidecomplete();merge()is reached only viaprocessMultiple. Don't trust the old "three-phase" description. before()runs per dataset item;transform()/assert()run once on the merged result - so abeforesees one config layer, atransformsees the whole.- Errors accumulate in
Context, never thrown mid-validation. Each element'scomplete()is an$isOk = $context->createChecker(); $isOk() && nextStep()short-circuit chain - thread any new validation step through the checker or it runs on already-rejected values. - Merging is schema-driven (2.0):
Schema::merge()takes aContext, strategy resolves asmergeWith(closure)→MergeMode(mergeMode()) → recursion only through item schemas. Ambiguous merges (colliding arrays with no schema guidance) add aMessage::CannotMergeerror, never a silent guess.AnyOfprobes which alternative both layers match (Context::isPartial= validation-only completion) and delegates to it. PreventMerging('_prevent_merging') was removed (BC break) —Processor::rejectPreventMerging()reports the key as an error; usemergeMode(MergeMode::Replace)instead.- Defaults are not merged into supplied arrays (
Type::$merge = false;mergeDefaults()is deprecated) - a partial input array stays partial. assert/castToare sugar overtransform- one$transformslist running in declaration order, so->assert()->castTo()differs from->castTo()->assert().defaultnull is notnullable(nullable()prepends'null|'to the type string); anullvalue coerces to[]when the default is an array.Structureis required-by-default and casts toobject, sodefault()throws on it.AnyOftries variants in order in a throwawayContextclone - losing variants' side effects (including transforms) are discarded.DynamicParametervalues get deferred validation (recorded inContext::dynamicsfor DI) - don't validate them eagerly.- User-facing how-to (the
Expect::API, castTo/Expect::fromobject mapping, building complex schemas) is manual material and lives in the public web docs.