OSS-ADR: Spatie Laravel OpenAPI CLI
How Spatie generates Artisan commands dynamically from OpenAPI specifications using Contract-First Development.
1. Context & Decision
The Problem:
When you build or consume an API, you often need to interact with it from the command line for testing, cron jobs, or administrative tasks. Usually, this means manually writing a bunch of repetitive Laravel Artisan commands (php artisan api:get-users, php artisan api:create-user) and wiring up Guzzle or Saloon clients to make the HTTP requests. This duplicates the API contract and leads to out-of-sync CLI tools.
The Alternatives:
- Manually writing an Artisan command for every endpoint.
- Using a generic HTTP client tool like curl or httpie (which lacks integration into your Laravel app's context).
The Decision:
We chose spatie/laravel-openapi-cli because it embraces Contract-First Development. Instead of writing code to define the CLI, you feed it an OpenAPI (.yaml or .json) specification, and it automatically generates dedicated, typed Artisan commands for every single endpoint.
2. The Integration
The Quick Win: Point the package to an OpenAPI spec file (like the Stripe API or your own internal API). Without writing a single line of PHP, you instantly have access to commands derived directly from the spec:
php artisan bookstore:get-books
php artisan bookstore:create-book --title="New Book" --author_id=123
Practical Usage: This is incredibly powerful when paired with Laravel Zero. You can build an entire CLI application to interact with a remote API in seconds. If the API spec changes, you simply update the YAML file, and your Artisan commands instantly adapt their arguments and options.
3. Under the Hood
Architecture & Design Patterns:
The package uses Dynamic Command Registration. Typically, Laravel commands are created by making a class that extends Illuminate\Console\Command and registering it in the Kernel.php.
However, laravel-openapi-cli reads the OpenAPI specification during the application boot process (likely in a Service Provider), parses the endpoints, and dynamically constructs and registers Command instances into the Artisan console application on the fly.
It maps OpenAPI parameters (query, path, and body) directly into Symfony Console Input Arguments and Options.
Code Masterclass: Bridging OpenAPI to Symfony Console
The tricky part of converting an OpenAPI spec to CLI commands is handling complex JSON request bodies. The package dynamically parses the JSON schema of an endpoint's request body and flattens it into CLI options. For example, an object property like {"user": {"name": "John"}} is converted into a dot-notation option like --user.name="John". This clever mapping bridges the gap between structured JSON and flat CLI arguments.
4. Consequences & Trade-offs
The Good: - Zero Boilerplate: Instantly turns any documented API into a fully functional CLI tool. - Single Source of Truth: Your OpenAPI spec dictates the CLI; they can never fall out of sync.
The Gotchas:
- Startup Overhead: Parsing a massive OpenAPI spec (like GitHub's or Stripe's) on every CLI boot can introduce noticeable lag to all php artisan commands.
- Dynamic Opacity: Because the commands don't exist as physical files in your app/Console/Commands directory, it can be confusing for developers who try to "find" the command class in their IDE.