krak/schema
krak/schema is a lightweight Laravel/PHP package for defining, validating, and managing application schemas. It provides a simple, framework-friendly API to keep schema logic organized, reusable, and easy to integrate into your project.
Architecture fit. krak/schema positions itself as a declarative data-schema layer — defining shape, then processing (validating, hydrating, or transforming) data against that shape. Architecturally, that places it in the application/domain boundary: the seam between transport (HTTP requests, queue payloads, webhooks, config files) and domain logic. In a Laravel codebase this is a crowded space already served natively by FormRequests, validation rules, casts, and API Resources, and by mature third-party options like spatie/laravel-data or Symfony Serializer.
What v0.2.0 changes about that assessment. Two things. First, the release formalises a struct vs. dict node distinction — a fixed, named-shape node versus an open map/dictionary node. That is the single most important modelling primitive for real payloads (a typed object versus a keyed collection, a record versus a lookup table), and its absence was a plausible reason the abstraction would have collapsed under nested and repeated structures. Its presence, plus the fact that dict usage was replaced by struct rather than coexisting, suggests the author is converging on a coherent type model rather than bolting on features. Second, the release references feature tests for Symfony config, which implies the schema layer can describe and round-trip Symfony Config component trees — i.e. the same declaration can drive configuration validation, not just request validation.
That second point is the differentiated use case the previous assessment could not identify. Laravel validation, FormRequests, and spatie/laravel-data are all request/payload-oriented; none of them give you a portable declaration that validates application or package configuration. If the package genuinely models config trees, it competes in a space with far weaker native coverage (raw config/*.php arrays validated by hand, or Symfony's TreeBuilder). The pitch becomes: one schema formalism for inbound payloads and config, with the Symfony config integration as proof the formalism is general. That is a real gap — but it is currently evidenced only by test code, not by documentation or a stated public API.
Integration feasibility. Mechanically still low-friction: Composer package, PSR-4, installable and exercisable in isolation. The Symfony config signal adds a caveat — if the package depends on symfony/config (or the dumper classes referenced in the release notes), we inherit a version-alignment constraint against whatever Symfony components Laravel already pulls in. That is usually benign but must be checked in composer why, not assumed. There is still no first-party Laravel service provider implied; wiring remains ours.
Technical risk. Still high, and the release cuts both ways.
Positive: the project is not dead. There is a maintainer actively reshaping the type model, writing feature tests, and fixing fidelity bugs. That is materially better than a frozen 2020 artifact.
Negative, and decisive for planning:
Key questions to resolve before any commitment.
spatie/laravel-data, Laravel validation, hand-written DTOs, and hand-rolled config validation? The config angle is the strongest candidate — verify it before falling back to the payload angle, where the gap is thinner.Stack fit. PHP/Laravel, Composer-managed — no infrastructure, no runtime services, no data store. A pure library, so fit is entirely about whether its abstraction matches our domain modelling style. Best fit remains as an internal anti-corruption layer: schemas describe our contracts and configuration, domain objects stay free of it.
Reframed pilot target. The config-schema capability changes where I would trial it. The highest-value, lowest-blast-radius pilot is now configuration validation — a package-level or service-level config tree declared once, validated on boot and in CI — rather than inbound HTTP payloads, where native tooling is strong and the package must beat it on merit. Config validation is a genuine gap, is not on the request hot path, and fails loudly in tests rather than in production.
Migration path. Do not adopt horizontally. Introduce it behind a thin internal interface (e.g., SchemaProcessor with parse(), validate(), hydrate(), and a describeConfig() surface) so the concrete implementation is swappable. That wrapper is the real deliverable; the package is an implementation detail. v0.2.0 is the concrete argument for this: a dict→struct rename should have been a one-file change in our codebase, not a sweep. Pin the dependency to an exact version, not a caret range, until the type model stabilises.
Compatibility. Build the compatibility matrix in a disposable branch before any product code: PHP 8.2/8.3, our Laravel support window, aligned Symfony component versions (composer why symfony/config), PHPUnit alignment, and a static-analysis pass. Treat red as a fork-and-patch decision point, not something to work around in application code.
Sequencing.
spatie/laravel-data, and hand-rolled config validation. Score on expressiveness, error quality, and boilerplate removed. Decide which of the two use cases (payload vs. config) is the wedge.Maintenance. The upstream-maintenance assumption needs revising but not reversing. Move from "assume zero" to "assume sporadic, uncommitted maintenance" — a release exists, a cadence does not. With no dependents and no published BC policy, the default planning assumption is still that breakage arrives without warning. Budget a named owner for a fork or a patch backlog. The hidden cost is not bug-fixing; it is the recurring tax of an unpinned dependency in every framework upgrade, now compounded by 0.x type-model churn.
Support. Still no vendor, no SLA, no community to escalate to. Internal documentation and a runbook remain mandatory. The v0.2.0 break adds a specific obligation: a written migration guide for our own schema declarations, since the package will not provide deprecation paths.
Scaling. As an in-process library it scales with the application; no separate capacity to plan. The scaling question is per-request cost on large or deeply nested payloads, and whether struct/dict nodes are compiled once or walked per validation — the 0.2.0 rewiring makes it worth re-benchmarking rather than trusting prior assumptions. The config use case is boot-time and therefore low-risk on throughput.
Failure modes to design against.
Ramp-up. The API surface is likely small enough for a senior engineer to become productive in one to two days, most of that reading tests rather than docs — and now specifically reading the struct/dict tests, since that distinction is the concept most likely to be misapplied (a struct used where a dict belongs produces subtly wrong validation rather than an error). Team-wide fluency is a separate cost: without official documentation, expect tribal knowledge to concentrate in one or two people unless we write the guide ourselves. Net position after v0.2.0: slightly more favourable than before — the project is alive and the type model is converging, and the config-validation use case gives us a differentiated reason to look — but the BC break confirms the package is not yet a foundation. Run the spike, lead with the config use case, and continue to weight the decision toward our internal abstraction rather than the package itself.
How can I help you explore Laravel packages today?