nette/schema
Nette Schema is a lightweight PHP library for validating and normalizing structured data. Define schemas to check arrays and objects, coerce values, and generate clear error messages for configs, APIs, and form input.
Architecture fit
nette/schema is a framework-agnostic schema definition and validation library (Nette component, but zero Nette framework coupling; single runtime dependency on nette/utils). It fits Laravel as a boundary layer — an anti-corruption layer at the point where untrusted or unstructured data enters the application (bootstrap config, JSON API payloads, third-party webhook bodies, YAML/JSON import jobs).Validator and FormRequest. It is not a replacement: Laravel's validator is rules-array-driven, request-scoped, and integrates with MessageBag/422 responses. nette/schema is schema-as-code — immutable, composable, type-object based, with normalization and default-merging built in. The overlap is a governance problem, not a technical one.Rule bridge, no container bindings). All integration glue is ours to own.Integration feasibility
composer require nette/schema (target ^1.3). No conflicts with Laravel's dependency tree; PHP 8.1+ requirement aligns with Laravel 10/11/12. Blocked only for legacy apps on PHP 8.0 or Laravel ≤9.Expect, Processor, Structure, ValidationException, Message. A competent Laravel engineer is productive in half a day.Technical risk
NOASSERTION. This is the single blocking item. The Nette ecosystem is conventionally BSD-3-Clause, but GitHub's classifier did not recognize the file. Do not adopt until legal explicitly clears it. Flag in the ADR; if it fails, fallback candidates are opis/json-schema (if OpenAPI is the source of truth) or respect/validation.nette/schema lands for APIs without an explicit boundary policy, the codebase will have FormRequest rules in some controllers and Expect::structure() in others. That's a durable consistency tax and a reviewer burden.Expect schemas are invisible to OpenAPI/TypeScript clients. If the org's API contract lives in OpenAPI, this package introduces a second source of truth for the same payload. That's a product-level risk (client breakage, stale docs), not just a code risk.Expect::array() strips numeric keys (silent data loss), default() does not fire on present-but-null keys, transform() used for side effects. These are training and code-review controls, not blockers.nette/di, nette/application, Latte, and much of the PHP ecosystem, so real-world install base is orders of magnitude larger than the low dependents figure reported here (that metric is not trustworthy for this package). Abandonment risk is low; direction risk is non-zero. Fork cost is small given the codebase size.process() call, but there's no streaming/collect-and-continue API. Fine for request validation; awkward for bulk import pipelines where you want per-record error reports without exception control flow.Key questions
NOASSERTION dependency, and who signs off?castTo() into plain readonly classes, or spatie/laravel-data, or neither? This decision is harder to reverse than the package choice itself.Stack fit
FormRequest behavior remains untouched until we deliberately change it.nette/utils overlap with other packages is possible but benign; verify in composer why nette/utils during the spike.Migration path (phased, reversible)
composer require in a scratch branch, validate one real config array, confirm no dependency conflicts, run the existing test suite.ValidationException::getMessages() (dot-path + code + message) into (a) Laravel's 422 JSON error envelope and (b) MessageBag for Blade forms. This adapter is the reusable asset; the package itself is commodity.FormRequest for flat, form-bound, user-facing inputs. No wholesale migration.Validator for simple forms, and generating client SDKs from schemas.Compatibility
phpstan.neon baseline so schema misuse is caught in CI, not production.Sequencing / dependencies
castTo vs. external data-object library vs. plain arrays) must be made before the pilot endpoint, because retrofitting DTOs after consumers depend on array shapes is a breaking change.^1.3, commit the lockfile, and add Composer audit + a documented upgrade cadence (this package ships minor releases frequently; budget a low-effort quarterly bump).Maintenance
Support
merge()/extend()/AnyOf()/castTo()/ranges/patterns/error codes.Scaling
Expect::structure() trees per request for large structures adds avoidable allocation. Construct once (static property, cached service, container singleton) and reuse.Expect::listOf over thousands of elements) and deeply recursive structures are the cost peaks; set payload size limits at the HTTP layer rather than relying on the schema.Failure modes
| Mode | Impact | Mitigation |
|---|---|---|
ValidationException uncaught on a code path lacking the adapter |
500 instead of 422 for bad client input | Mandatory adapter in the base API exception handler; test coverage per endpoint |
| Overly strict schema shipped to live clients | Client breakage / outage | Additive-only schema evolution; version schemas; stage-rollout and contract tests |
| Overly permissive schema | Validation theater — no protection but false confidence | Adversarial fixtures (valid and invalid) required in review |
Silent normalization surprises (Expect::array key stripping, null vs missing defaults) |
Data-quality incidents, hard to trace | Explicit cookbook examples, listOf vs array guidance, lint rule / review checklist |
| Schema drift from OpenAPI/docs | Stale docs, client mismatches | Decide contract of record before adoption; if code wins, generate docs from schemas |
transform() used for side effects |
Hidden I/O, non-deterministic behavior, test flake | Written rule: transform() is pure coercion only |
| Package stops receiving PHP-version patches | Blocked framework upgrades | Small codebase enables fork/vendor; transitive ubiquity creates ecosystem pressure; monitor at each PHP upgrade |
| License status unresolved at audit | Forced removal mid-project | Block adoption on legal clearance up front |
Ramp-up
Structure::merge()/extend()/skipDefaults(), AnyOf, castTo, ranges/patterns), plus our error-mapping adapter and the boundary policy.How can I help you explore Laravel packages today?