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.
krak/schema is a small, low-level package for declaring data schemas and running values through them. It is not a Laravel-first package, so expect to wire it in yourself. Version 0.2.0 is the current release and contains a BC change from 0.1.0: the package now distinguishes struct nodes (fixed, named keys) from dict nodes (dynamic/map-like keys), and prior dict usage was replaced with struct. Read the changelog before upgrading.
composer require krak/schema:^0.2. If you already used 0.1.0, do not float blindly across the major/minor boundary without reading the release notes and testing. Check resolution with composer why-not krak/schema if needed.src/ on GitHub before writing code. Look for the primary Schema class, the node classes, and specifically how struct and dict are represented after 0.2.0.composer.json for the autoload namespace (likely Krak\Schema\) and the PHP version floor. This tells you what language features the internals rely on. The package is old enough that PHP 8.1/8.2 deprecations are worth checking.isset()/is_string() ladders.// Illustrative shape — verify exact API names in src/
use Krak\Schema\Schema;
$schema = Schema::make([
'id' => 'int',
'email' => 'string',
'tags' => ['string'], // collection/list semantics, not a fixed-key struct
]);
$clean = $schema->process($request->all());
Start with one payload type, get it working end-to-end in a test, then expand.
One schema per payload type. Create a small class per domain concept (CreateOrderSchema, WebhookPayloadSchema) rather than inline arrays scattered across controllers. This makes them testable in isolation and gives you a single place to change when an upstream contract moves:
class CreateOrderSchema extends Schema
{
protected function definition(): array
{
return [
'customer_id' => 'int',
'lines' => [[/* line schema */]],
];
}
}
Process at the boundary, not in the model. Invoke schemas in FormRequests, controllers, or queued-job handlers — wherever external data enters. Keep Eloquent models and domain services working with already-normalized arrays/DTOs. This mirrors how you'd use Laravel's Validator, so it slots into existing habits.
Use structs for records, dicts for maps. After 0.2.0, be deliberate about the distinction:
customer_id, email, lines. This is what most request/resource payloads should be.If you are migrating from 0.1.0, audit every place you previously used dict for what was really a fixed-key record. That is the BC break: those should now be struct definitions. Keep true maps as dict.
Compose schemas for nesting. Build a library of leaf schemas (AddressSchema, MoneySchema) and compose upward. This is where a schema package earns its keep over inline validation — reuse and nesting — so lean into it. Check whether 0.2.0 struct nodes compose more cleanly than the old dict-based nesting; they usually do.
Pipeline processing. If the package exposes ordered processors, treat them as a normalization pipeline: coerce types → apply defaults → validate constraints. Keep each step single-purpose so failures are easy to localize.
Laravel integration. There is no service provider you should expect to be auto-discovered. Register schemas as singletons in AppServiceProvider if they're expensive to build, or just new them — they're cheap value objects. Consider a Schema::processOrFail() wrapper that throws a ValidationException so failures surface through Laravel's normal error handling:
try {
$data = $schema->process($input);
} catch (\Throwable $e) {
throw ValidationException::withMessages(['payload' => $e->getMessage()]);
}
Test schemas directly. For each schema, assert: valid input passes and is normalized as expected; missing required fields fail; wrong types fail or coerce (know which); extra/unknown keys are handled predictably; struct and dict schemas behave differently in the expected ways. These tests are cheap and become your real documentation. Add explicit regression tests when upgrading from 0.1.0 to 0.2.0.
0.2.0 is a BC release. The struct/dict split is the headline. If you are on 0.1.0, pin before upgrading and run your schema tests. Search your code for every dict usage and classify it: fixed-key record → struct; true dynamic map → keep dict. Tests that relied on old dict semantics for records will fail or, worse, silently accept the wrong shape.app/Support/ and dropping the Composer dependency — for a package this small that is often the safer long-term move.src/. Budget an hour to read every class — it's small enough to read fully, which is the main advantage of an obscure package. After 0.2.0, specifically read the struct and dict node classes.null to non-nullable internals). Run your test suite with E_ALL and a strict error handler before trusting it in production."5" become 5? Does "yes" become true? Does null satisfy a required field? Write explicit tests for each, because these semantics are the package's whole value proposition and the least likely thing to be documented.0.2.0 changed the extension points around structs and dicts.0.1.0 → 0.2.0 migration, also log whether the failing schema was expected to be a struct or a dict; that is the most likely source of upgrade breakage.How can I help you explore Laravel packages today?