The openai-php client library setup is straightforward with Composer, but a few configuration details trip up first-time users. This tutorial builds a small CLI script that talks to the OpenAI Chat Completions API, then shows how to adapt the same client for Laravel and any OpenAI-compatible gateway.
Prerequisites
Confirm your environment before writing code:
- PHP 8.1 or newer (the client uses enums, readonly classes, and union types)
- Composer 2.x installed globally or locally
- An API key from OpenAI, or a key for an OpenAI-compatible service
- The
curlextension enabled (PSR-18 clients typically use it) - Basic comfort with PSR-4 autoloading and a terminal
For a plain PHP project, create a working directory and run composer init -n to generate a baseline composer.json. Laravel users already meet the PHP and Composer requirements.
Install the package
Run the require command in your project root:
composer require openai-php/client
Composer resolves the latest stable openai-php/client (v0.9.x as of writing) and pulls in php-http/discovery plus a PSR-18 HTTP client implementation. After the install finishes, inspect the lock file:
composer show openai-php/client
Expected output includes a line like versions: * v0.9.0 and a list of dependencies. Your composer.json now contains:
{
"require": {
"openai-php/client": "^0.9",
"php": "^8.1"
}
}
Verify the autoloader exists:
test -f vendor/autoload.php && echo "autoloader present"
Output: autoloader present.
Configure the client
The client is constructed through the OpenAI\OpenAI factory. The minimal form takes an API key string:
<?php
require __DIR__ . '/vendor/autoload.php';
use OpenAI\OpenAI;
$apiKey = getenv('OPENAI_API_KEY');
if ($apiKey === false) {
fwrite(STDERR, "OPENAI_API_KEY not set\n");
exit(1);
}
$client = OpenAI::client($apiKey);
echo "Client ready\n";
Run it after exporting your key:
export OPENAI_API_KEY=sk-...
php script.php
Expected output:
Client ready
The factory also accepts an associative options array for baseUri, timeout, headers, organization, and logger. For example, to set a 10-second timeout:
$client = OpenAI::client($apiKey, timeout: 10);
First chat completion
Make a non-streaming call to gpt-4o-mini. The method chain mirrors the REST path: chat()->create().
<?php
require __DIR__ . '/vendor/autoload.php';
use OpenAI\OpenAI;
$client = OpenAI::client(getenv('OPENAI_API_KEY'));
$response = $client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'system', 'content' => 'You are a terse PHP expert.'],
['role' => 'user', 'content' => 'What is the difference between === and == in PHP?'],
],
'temperature' => 0.2,
]);
echo $response->choices[0]->message->content . "\n";
Expected output (abridged):
=== checks both value and type; == checks only value after type juggling. Use === to avoid surprises.
The returned object is a typed response struct. Dump the usage field to see token accounting:
printf(
"Tokens: prompt=%d completion=%d total=%d\n",
$response->usage->promptTokens,
$response->usage->completionTokens,
$response->usage->totalTokens
);
A sample print:
Tokens: prompt=28 completion=19 total=47
Streaming responses
For interactive apps, stream tokens as they arrive. The createStreamed() method returns a Generator:
<?php
require __DIR__ . '/vendor/autoload.php';
use OpenAI\OpenAI;
$client = OpenAI::client(getenv('OPENAI_API_KEY'));
$stream = $client->chat()->createStreamed([
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'user', 'content' => 'List three PHP array functions.'],
],
'temperature' => 0.5,
]);
foreach ($stream as $chunk) {
$delta = $chunk->choices[0]->delta->content ?? '';
echo $delta;
flush();
}
echo "\n";
You will see incremental output printed without waiting for the full response. Each $chunk has a ->choices[0]->finishReason that becomes "stop" on the final packet.
Integrate with Laravel
In Laravel, bind the client as a singleton in a service provider. Store the key in .env and config/services.php:
# .env
OPENAI_API_KEY=sk-...
// config/services.php
return [
// ...
'openai' => [
'key' => env('OPENAI_API_KEY'),
],
];
Create app/Providers/OpenAIProvider.php:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use OpenAI\Client;
use OpenAI\OpenAI;
class OpenAIProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(Client::class, function () {
return OpenAI::client(config('services.openai.key'));
});
}
}
Register it in bootstrap/providers.php (Laravel 11) or config/app.php (older). Then inject the client into a controller:
<?php
namespace App\Http\Controllers;
use OpenAI\Client;
use Illuminate\Http\Request;
class AskController extends Controller
{
public function __invoke(Request $request, Client $client)
{
$reply = $client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [['role' => 'user', 'content' => $request->input('q')]],
]);
return response()->json(['answer' => $reply->choices[0]->message->content]);
}
}
This keeps the openai-php client library setup isolated from business logic and makes unit testing trivial with a mocked Client.
Target an OpenAI-compatible gateway
The client speaks the OpenAI REST contract, so you can point it at any compatible endpoint by passing baseUri. For example, n4n.ai provides a single OpenAI-compatible endpoint that fronts 240+ models and automatically falls back when a provider is rate-limited. Set the base URI and use the same method calls:
<?php
require __DIR__ . '/vendor/autoload.php';
use OpenAI\OpenAI;
$client = OpenAI::client(
apiKey: getenv('N4N_API_KEY'),
baseUri: 'https://api.n4n.ai/v1',
);
$response = $client->chat()->create([
'model' => 'anthropic/claude-3.5-sonnet',
'messages' => [['role' => 'user', 'content' => 'Ping']],
'temperature' => 0.0,
]);
echo $response->choices[0]->message->content . "\n";
Some gateways, including n4n.ai, honor client routing directives and forward provider cache-control hints, so you can attach cache_control metadata in messages when the upstream model supports it. The client passes extra keys through untouched.
Handle errors and retries
Network failures and rate limits happen. Catch OpenAI\Exceptions\ErrorException at the boundary:
<?php
require __DIR__ . '/vendor/autoload.php';
use OpenAI\OpenAI;
use OpenAI\Exceptions\ErrorException;
$client = OpenAI::client(getenv('OPENAI_API_KEY'));
try {
$response = $client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [['role' => 'user', 'content' => 'Hello']],
]);
} catch (ErrorException $e) {
fwrite(STDERR, sprintf("API error %s: %s\n", $e->getCode(), $e->getMessage()));
exit(1);
}
echo $response->choices[0]->message->content . "\n";
The library does not retry by default. For transient 429s, wrap the call in a short backoff loop, or supply a PSR-18 client with retry middleware via the httpClient option.
Inspect requests during development
To see the raw payload, swap in a PSR-3 logger:
$client = OpenAI::client(
getenv('OPENAI_API_KEY'),
logger: new \Psr\Log\StreamLogger(fopen('php://stderr', 'w')),
);
Remove the logger in production to avoid leaking token counts or prompts to logs.
Wrapping up the setup
The openai-php client library setup boils down to three steps: require the package, build a client with a key, and call the resource methods. From there, streaming, Laravel binding, and gateway swapping are configuration changes, not code rewrites. Use typed responses, catch exceptions at the edge, and keep your API key out of source control.