Install the Package:
composer require jane-php/open-api-3
Ensure your Laravel project uses PHP 8.0+ and has a PSR-7 HTTP client (e.g., guzzlehttp/psr7).
Prepare Your OpenAPI Spec:
config/api-spec.json.{
"openapi": "3.0.0",
"info": { "title": "My API", "version": "1.0.0" },
"paths": { ... }
}
Generate the Client:
Add a custom Artisan command (e.g., GenerateApiClient) in app/Console/Commands:
use Jane\OpenApi\Generator;
use Jane\OpenApi\Reader;
use Illuminate\Console\Command;
class GenerateApiClient extends Command
{
protected $signature = 'api:generate {--spec= : Path to OpenAPI spec}';
protected $description = 'Generate a PHP client from an OpenAPI spec';
public function handle()
{
$specPath = $this->option('spec') ?? config('api.spec_path');
$reader = new Reader();
$spec = $reader->readFromFile($specPath);
$generator = new Generator();
$client = $generator->generate($spec, 'ApiClient');
// Save to app/ApiClient/Client.php (or use a trait)
file_put_contents(app_path('ApiClient/Client.php'), $client);
$this->info('Client generated successfully!');
}
}
Register the command in app/Console/Kernel.php:
protected $commands = [
\App\Console\Commands\GenerateApiClient::class,
];
First Usage: Call the generated client in a service:
use App\ApiClient\Client;
class MyService
{
public function __construct(private Client $client) {}
public function fetchUser($id)
{
return $this->client->get("/users/{$id}");
}
}
Bind the client in a service provider:
$this->app->bind(Client::class, function ($app) {
$spec = (new Reader())->readFromFile(config('api.spec_path'));
return (new Generator())->generate($spec, 'ApiClient');
});
PSR-7 Compliance:
Psr\Http\Message\RequestInterface and ResponseInterface.$client = new \GuzzleHttp\Client(['handler' => HandlerStack::create()]);
$response = $client->send($generatedRequest);
Method Chaining:
$this->app->bind('auth.client', function ($app) {
return $app->make(Generator::class)
->generate($authSpec, 'AuthClient');
});
Dynamic Spec Loading:
.env):
$specPath = config('services.api.spec');
$spec = (new Reader())->readFromFile($specPath);
Authentication:
securitySchemes to auto-generate auth headers:
# api-spec.yaml
components:
securitySchemes:
api_key:
type: apiKey
in: header
name: X-API-KEY
The generated client will include methods like withApiKey($token).Request/Response DTOs:
$user = $client->users()->create([
'name' => 'John Doe',
'email' => 'john@example.com'
]);
// $user is auto-cast to a typed object (e.g., `UserResponse`)
HTTP Client Integration:
Http::macro('apiGet', function ($uri, $spec) {
$client = (new Generator())->generate($spec, 'DynamicClient');
return $client->get($uri);
});
Middleware for Auth:
$client = $generator->generate($spec, 'ApiClient');
$client->getMiddleware()->push(
\App\Http\Middleware\AuthenticateApi::class
);
Caching Generated Clients:
$client = Cache::remember('api.client', now()->addHours(1), function () {
return $generator->generate($spec, 'CachedClient');
});
Testing with Factories:
$response = new \Psr\Http\Message\Response(
200,
[],
json_encode(['id' => 1, 'name' => 'Test'])
);
$client->getMockHandler()->append(
new \Http\Mock\Handler\MockResponse($response)
);
Event Dispatching:
$client->getEventDispatcher()->addListener(
'api.request.sent',
function ($request) {
event(new ApiRequestSent($request));
}
);
Plugin System:
$generator->addPlugin(new class implements \Jane\OpenApi\Plugin\PluginInterface {
public function generate(\Jane\OpenApi\Generator $generator) {
// Custom logic
}
});
Async Clients:
$loop = React\EventLoop\Factory::create();
$client = new \React\Http\Client($loop);
$promise = $client->sendRequest($generatedRequest);
$promise->then(...);
Spec Validation:
zircote/swagger-php):
$validator = new \Zircote\Swagger\Validator();
$validator->validate(file_get_contents($specPath));
Spec Parsing Errors:
paths or servers) cause generation failures.spectral or openapi-linter in CI:
npx @stoplight/spectral lint api-spec.yaml
spec:validate Artisan command:
$this->call('vendor:publish', ['--provider' => 'Zircote\Swagger\SwaggerValidatorServiceProvider']);
Circular References:
$ref loops in specs (e.g., self-referencing schemas) crash the generator.allOf/anyOf instead of circular $ref.PHP 8 Attributes:
#[\ArrayShape(['id' => 'int'])]), which break on older PHP versions.PSR-7 Handler Conflicts:
$handler = new \Http\Mock\Handler();
$client = new \GuzzleHttp\Client(['handler' => $handler]);
Namespace Collisions:
User).$generator->setTemplate('class', 'app/ApiClient/Templates/class.stub');
Verbose Generation:
$generator->setDebug(true);
Inspect Generated Code:
tmp/ directory:
$generator->setOutputDir(__DIR__.'/tmp');
Middleware Debugging:
$client->getMiddleware()->push(function ($request,
How can I help you explore Laravel packages today?