v3.0.0
v3 is a significant release that raises the platform floor, adds a suite of developer-experience tools, and fixes several validation edge cases.
Breaking Changes
Minimum requirements
- PHP 8.3+ required (was 8.1+)
- Laravel 12+ required (was 10+)
New config key
A new error_format key is required in config/spectator.php. If you published the config in v2, add:
'error_format' => env('SPECTATOR_ERROR_FORMAT', 'text'),No behaviour change unless you set SPECTATOR_ERROR_FORMAT=json.
Internal type changes (extenders only)
If you extend Spectator's validator classes, note that:
'read'/'write'strings inAbstractValidatorare nowValidationMode::Read/ValidationMode::Writeenum casesFormat::TEXT_GREENstyle class constants are nowFormat::TextGreenstring-backed enum cases
These are internal details — if you only use the public facade and TestResponse assertions, no action is required.
See UPGRADE.md for the full list.
New Features
Artisan Commands
Four new artisan commands provide visibility into your spec and its relationship to your application.
spectator:validate — lint a spec file before tests run:
php artisan spectator:validate --spec=Api.v1.yml
php artisan spectator:validate --spec=Api.v1.yml --format=jsonspectator:coverage — list every operation defined in the spec:
php artisan spectator:coverage --spec=Api.v1.ymlspectator:routes — cross-reference spec operations against your registered Laravel routes:
php artisan spectator:routes --spec=Api.v1.ymlOutputs matched, unimplemented, and undocumented routes at a glance.
spectator:stubs — generate skeleton test classes from a spec, ready to fill in:
php artisan spectator:stubs --spec=Api.v1.yml --output=tests/ContractOperations are grouped by tag (falling back to path segment), producing one test class per group with one markTestIncomplete method per operation.
All commands support --format=json for machine-readable output.
PHPUnit Coverage Extension
SpectatorExtension is a PHPUnit 11 extension that tracks which spec operations are exercised across the full test run and prints a summary table when the suite finishes. Configure a minimum threshold to fail CI when coverage drops:
<extensions>
<bootstrap class="Spectator\Coverage\SpectatorExtension">
<parameter name="min_coverage" value="80"/>
</bootstrap>
</extensions>JSON Error Format
Validation errors can now be emitted as structured JSON instead of ANSI-coloured text — useful for CI log parsers, LLM-driven workflows, and anything that processes test output programmatically:
SPECTATOR_ERROR_FORMAT=jsonOr toggle per test:
Spectator::useJsonErrors();
Spectator::useTextErrors();A failed assertion with JSON errors produces:
{ "errors": ["The data (null) must match the type: string"] }Fluent Path Prefix API
Spectator::withPathPrefix('v1');As an alternative to setting SPECTATOR_PATH_PREFIX in .env.
AI & CI Integration
The combination of spectator:validate --format=json, SPECTATOR_ERROR_FORMAT=json, and SpectatorExtension makes Spectator a natural fit for AI-driven development workflows — structured outputs at every stage mean errors and coverage gaps can be piped directly into LLM toolchains.
Bug Fixes
-
Nullable inside
additionalPropertiesnot migrated — OpenAPI 3.0nullable: trueon dictionary value schemas was silently skipped during the 3.0 to 3.1 nullable migration. Values likenullwere incorrectly rejected. (#215) -
Comma-separated values in header parameters —
explode: falsewithtype: arrayonin: headerparameters now correctly splits the comma-separated string into an array before validation, matching the existing behaviour for query parameters. (inspired by #213) -
TypeError with empty request body on
allOfschemas — sending an empty body against a requiredallOfrequest body no longer throws aTypeError. The error is now a clean "Request body required!" validation failure. (#216) -
Nullable properties inside
allOf—nullable: trueon properties nested withinallOfarms is correctly migrated to 3.1-styletype: [T, null]. (#212)
Internals
- PHP enums replace string and class constants throughout (
ValidationMode,Format,Source) - First-class callables,
readonlyproperties, andmatchexpressions used where appropriate - PHPStan level 6 enforced in CI with zero errors
- Test suite expanded from ~180 to 248 test cases
- GitHub Actions matrix covers PHP 8.3/8.4 x Laravel 12/13
- All dependency version constraints loosened on the upper bound so future Laravel versions work without a constraint update