Skip to content

Philosophy

The design of openapi-laravel follows a small set of principles. They are the same principles as the sibling project, openapi-zod-ts, translated to the Laravel ecosystem.

Code follows the contract, never the other way around. This is the opposite of annotation-driven tools where your PHP generates the spec. When the OpenAPI document changes, you re-run the generator and the code catches up. There is no second authority to reconcile by hand.

Generated classes are readable PHP in your repo. Review them, commit them, read them in a diff. There is no opaque runtime and no reflection magic you cannot follow. If you ever want to stop using the generator, the code is already yours and keeps working.

Validation rules are emitted verbatim from spec constraints, not guessed from property types at runtime. If the spec says maxLength: 255, the generated rules() says max:255. What the contract states is what gets validated, and you can read exactly why each rule is there.

Stable ordering everywhere: properties, rules, enum cases, files. Regenerating produces a byte-identical diff or no diff at all, so the generator is safe to run in CI and commit. A change in the output always means a change in the spec or the generator, never incidental churn.

PHP 8.2 and newer, Laravel 11, 12, and 13, spatie/laravel-data v4. No legacy shims and no compatibility layers for versions that are out of support. The output uses promoted readonly properties, native backed enums, and current laravel-data idioms, because that is the code you would write today.

A code generator has a wide blast radius: a subtle regression touches every project that runs it. So the generator is held to a high bar, and the bar is behavioral, not just syntactic.

Generated validation is differentially tested against the spec, not just compiled. Behavioral round-trip tests load the generated classes into a real Laravel app (Orchestra Testbench) and run real payloads through Laravel’s Validator: valid payloads pass, while a missing required field, a malformed email, and a below-minimum integer are each rejected (tests/Feature/Emitter/RoundTripTest.php). The end-to-end demo goes further and proves 422 responses over real HTTP from the spec-derived rules(). Pest’s native mutation testing deliberately breaks the emitter and fails if the suite does not notice.

The differential validation oracle (shipped in 0.7.0, #23) asserts the contract directly. A constraint catalog pins each supported constraint with payloads the spec accepts and payloads it rejects; for every entry the harness generates the Data class and runs every payload through the real Laravel Validator. What the spec rejects, the generated rules() must reject, and what the spec accepts, they must accept: any disagreement fails the suite. The few acknowledged gaps are pinned by a known-gap ratchet (tests/Conformance/DIFFERENTIAL_FINDINGS.md), each tracked by an open issue, so gaps cannot accumulate silently and a fixed gap must be removed from the list.

The corpus gate. The generator is tested against the published OpenAPI documents of Stripe, GitHub, OpenAI, Slack, Twilio, and 130 others. Every one of the 135 must parse, generate PHP that compiles (php -l), and resolve every class reference, on every CI run. A 0.7.0 robustness sweep ran the generator over 9 large public API specs as test inputs (Stripe, GitHub, Box, Adyen, Asana, Sentry, Twilio among them): all 13,378 generated files compile clean. Separately, PHPStan at max level and 100% type coverage run on the generator source and the committed snapshots of generated output, which are diff-checked so any change to the generator is visible in review. The corpus is shared with the sibling TypeScript project, so both tools face the same real-world input.