PHP powers a large part of the web, but most AI tutorials focus on Python. This guide fills that gap: a complete OpenAI PHP tutorial covering installation, text generation, chat completions, streaming, and function calling — all with working code you can drop into any PHP project, Laravel or plain PHP.
Prerequisites
- PHP 8.1+
- Composer installed
- An OpenAI API key (platform.openai.com/api-keys)
Installation
The official openai-php/client library is the standard choice:
composer require openai-php/clientStore your API key in .env (never hardcode it):
OPENAI_API_KEY=sk-proj-...Basic Setup
<?php
require 'vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$client = OpenAI::client($_ENV['OPENAI_API_KEY']);If you’re not using a .env loader, you can also pass the key directly (fine for local dev):
$client = OpenAI::client(getenv('OPENAI_API_KEY'));Text Generation (Chat Completions)
The chat.completions.create endpoint is the main interface for GPT-4o and GPT-4o-mini:
<?php
$response = $client->chat()->create([
'model' => 'gpt-4o',
'messages' => [
['role' => 'system', 'content' => 'You are a helpful PHP developer assistant.'],
['role' => 'user', 'content' => 'Explain what a PHP trait is in two sentences.'],
],
'max_tokens' => 256,
]);
echo $response->choices[0]->message->content;
// Output: A trait in PHP is a mechanism for code reuse...
echo "\nTokens used: " . $response->usage->totalTokens;Multi-Turn Conversations
To build a conversation with memory, maintain the messages array across turns:
<?php
$messages = [
['role' => 'system', 'content' => 'You are a helpful assistant.'],
];
function chat(array &$messages, string $userMessage, $client): string
{
$messages[] = ['role' => 'user', 'content' => $userMessage];
$response = $client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => $messages,
'max_tokens' => 512,
]);
$reply = $response->choices[0]->message->content;
$messages[] = ['role' => 'assistant', 'content' => $reply];
return $reply;
}
echo chat($messages, 'What is a closure in PHP?', $client) . "\n";
echo chat($messages, 'Give me a code example.', $client) . "\n";Streaming Responses
Streaming lets you display the response token by token as it arrives — essential for chatbots and user-facing apps:
<?php
// Set headers for Server-Sent Events
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');
$stream = $client->chat()->createStreamed([
'model' => 'gpt-4o',
'messages' => [
['role' => 'user', 'content' => 'Explain dependency injection in PHP.'],
],
'max_tokens' => 512,
]);
foreach ($stream as $response) {
$text = $response->choices[0]->delta->content ?? '';
if ($text !== '') {
echo "data: " . json_encode(['text' => $text]) . "\n\n";
ob_flush();
flush();
}
}
echo "data: [DONE]\n\n";On the frontend, consume the SSE stream with EventSource or fetch with a reader:
const eventSource = new EventSource('/api/stream');
eventSource.onmessage = (e) => {
if (e.data === '[DONE]') {
eventSource.close();
return;
}
const { text } = JSON.parse(e.data);
document.getElementById('output').textContent += text;
};Function Calling (Tool Use)
Function calling lets the model invoke your PHP functions when it decides it needs external data:
<?php
$tools = [
[
'type' => 'function',
'function' => [
'name' => 'get_product_price',
'description' => 'Get the current price of a product by SKU',
'parameters' => [
'type' => 'object',
'properties' => [
'sku' => [
'type' => 'string',
'description' => 'Product SKU code',
],
],
'required' => ['sku'],
],
],
],
];
$messages = [
['role' => 'user', 'content' => 'What is the price of product SKU-1234?'],
];
$response = $client->chat()->create([
'model' => 'gpt-4o',
'messages' => $messages,
'tools' => $tools,
'tool_choice' => 'auto',
]);
$choice = $response->choices[0];
if ($choice->finishReason === 'tool_calls') {
$toolCall = $choice->message->toolCalls[0];
$args = json_decode($toolCall->function->arguments, true);
// Execute your actual function
$price = getProductPrice($args['sku']);
// Add tool result to messages and call again
$messages[] = ['role' => 'assistant', 'content' => null, 'tool_calls' => $response->choices[0]->message->toolCalls];
$messages[] = [
'role' => 'tool',
'tool_call_id' => $toolCall->id,
'content' => (string) $price,
];
$finalResponse = $client->chat()->create([
'model' => 'gpt-4o',
'messages' => $messages,
]);
echo $finalResponse->choices[0]->message->content;
}
function getProductPrice(string $sku): float
{
// Query your database
return 29.99; // example
}Image Analysis (Vision)
GPT-4o supports image inputs alongside text:
<?php
// Pass image as URL
$response = $client->chat()->create([
'model' => 'gpt-4o',
'messages' => [
[
'role' => 'user',
'content' => [
['type' => 'text', 'text' => 'What does this error message say?'],
[
'type' => 'image_url',
'image_url' => ['url' => 'https://example.com/screenshot.png'],
],
],
],
],
'max_tokens' => 512,
]);
echo $response->choices[0]->message->content;
// Pass image as base64
$imageData = base64_encode(file_get_contents('/path/to/image.png'));
$response = $client->chat()->create([
'model' => 'gpt-4o',
'messages' => [
[
'role' => 'user',
'content' => [
['type' => 'text', 'text' => 'Describe this image.'],
[
'type' => 'image_url',
'image_url' => ['url' => "data:image/png;base64,{$imageData}"],
],
],
],
],
]);Laravel Integration
In a Laravel application, register the client as a singleton in a service provider:
<?php
// app/Providers/OpenAIServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use OpenAI;
use OpenAI\Client;
class OpenAIServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(Client::class, function () {
return OpenAI::client(config('services.openai.key'));
});
}
}# config/services.php
'openai' => [
'key' => env('OPENAI_API_KEY'),
],<?php
// app/Http/Controllers/ChatController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use OpenAI\Client;
class ChatController extends Controller
{
public function __construct(private readonly Client $openai) {}
public function ask(Request $request): string
{
$response = $this->openai->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'user', 'content' => $request->input('message')],
],
'max_tokens' => 512,
]);
return $response->choices[0]->message->content;
}
}Error Handling
<?php
use OpenAI\Exceptions\UnserializableResponse;
use OpenAI\Exceptions\TransporterException;
try {
$response = $client->chat()->create([
'model' => 'gpt-4o',
'messages' => [['role' => 'user', 'content' => $userMessage]],
'max_tokens' => 512,
]);
return $response->choices[0]->message->content;
} catch (\Exception $e) {
// Check for rate limit (HTTP 429)
if (str_contains($e->getMessage(), '429') || str_contains($e->getMessage(), 'rate_limit')) {
sleep(2);
// retry logic here
}
// Check for invalid API key (HTTP 401)
if (str_contains($e->getMessage(), '401') || str_contains($e->getMessage(), 'invalid_api_key')) {
throw new \RuntimeException('Invalid OpenAI API key');
}
throw $e;
}Cost Control
- Use
gpt-4o-minifor simple tasks (classification, extraction) — it’s 17x cheaper than gpt-4o - Set
max_tokensexplicitly — never let the model return unlimited output - Cache responses in Redis for identical or near-identical prompts
- Log token usage per request to track costs early
<?php
// Log token usage for cost tracking
$response = $client->chat()->create([...]);
$usage = $response->usage;
\Log::info('OpenAI usage', [
'prompt_tokens' => $usage->promptTokens,
'completion_tokens' => $usage->completionTokens,
'total_tokens' => $usage->totalTokens,
// gpt-4o pricing: $2.50/1M input, $10/1M output
'estimated_cost_usd' => ($usage->promptTokens / 1_000_000 * 2.50)
+ ($usage->completionTokens / 1_000_000 * 10),
]);Summary
The openai-php/client library makes it straightforward to add GPT-4o to any PHP application. The key patterns: use chat().create for standard completions, add SSE streaming for user-facing chatbots, function calling for tool use, and register a singleton in Laravel for clean dependency injection.
For working with Claude API in PHP instead, the pattern is similar — see the Claude API docs for the PHP SDK options.
Subscribe to my newsletter — practical guides on Claude API, AI agents, RAG, and automation.