Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Symfony Laravel Package

openai-php/symfony

Symfony bundle for the OpenAI PHP client, making it easy to integrate GPT models, chat completions, embeddings, and other OpenAI API features into Symfony apps with simple configuration and dependency injection.

View on GitHub
Deep Wiki
Context7

Getting Started

The v0.19.0 release is a single-line dependency bump: openai-php/client moved to ^0.19.0. Nothing in the bundle's own API surface changed, so the practical impact is entirely in the client layer underneath — which is exactly where you should look first.

Install and wire up

composer require openai-php/symfony

This is the Symfony integration package. If you are in a Laravel app, the idiomatic path is openai-php/laravel, which wraps the same openai-php/client core. Either way, the underlying client version is what this release changes, so treat the two as a pair and keep them in lockstep.

Minimal config (Symfony bundle style):

# config/packages/openai.yaml
openai:
    api_key: '%env(OPENAI_API_KEY)%'
    organization: '%env(OPENAI_ORGANIZATION)%'  # optional

Where to look first

  1. vendor/openai-php/client/composer.json — confirm the resolved version after composer update, since ^0.19.0 on a 0.x package is a narrower promise than it looks.
  2. vendor/openai-php/client/CHANGELOG.md — the client's own 0.18 → 0.19 delta is the real release note here; the bundle changelog won't tell you.
  3. The autowired OpenAI\Client service (or the OpenAI facade in the Laravel wrapper) — this is your single entry point for everything else.
  4. config/packages/openai.yaml for the bundle defaults (base URI, timeout, retry budget).

First real use case

Rather than reaching for the client directly in a controller, start with one small, named service so the client never leaks into your HTTP layer:

final class SupportReplyDrafter
{
    public function __construct(private OpenAI\Client $client) {}

    public function draft(string $ticketBody): string
    {
        $response = $this->client->chat()->create([
            'model' => config('services.openai.model', 'gpt-4o-mini'),
            'messages' => [
                ['role' => 'system', 'content' => 'Draft a concise support reply.'],
                ['role' => 'user', 'content' => $ticketBody],
            ],
            'temperature' => 0.3,
        ]);

        return $response->choices[0]->message->content;
    }
}

That one class gives you a test double boundary, a place to hang retries, and a single spot to swap model names.

Implementation Patterns

Constructor injection, never app() / $container->get(). The client is a stateless HTTP wrapper; it's safe to inject as a singleton and cheap to resolve. Binding it behind your own interface (CompletionService, Embedder) is what makes the rest of the app testable without touching the network.

Structured output over prose parsing. Ask for JSON and decode defensively — models drift and occasionally emit trailing prose or fences:

$raw = $response->choices[0]->message->content;
$data = json_decode(trim(preg_replace('/^```(?:json)?|```$/m', '', $raw)), true, flags: JSON_THROW_ON_ERROR);

Better still, use the client's JSON-mode / response-format support where the model allows it, then still validate with a Laravel FormRequest or a Validator::make() before trusting field values.

Streaming to the browser. For chat UIs, use the streaming path and pipe tokens out through a StreamedResponse. Remember to disable output buffering and set X-Accel-Buffering: no if you're behind nginx, or tokens will arrive in one lump. Do not do this inside a queued job — stream only from a live request.

Batch and offload anything non-interactive. Summarisation, embedding backfills, classification sweeps: push to a queue job with $tries = 3 and a backoff() that respects rate limits. Keep the client call out of the job's constructor and do it in handle() so retries get a fresh instance.

Embeddings and RAG. Generate embeddings per document chunk on write, store the vector (pgvector is the least-friction option in a Laravel stack), and query by cosine distance in SQL rather than pulling candidates into PHP. Re-embed the whole corpus if you change embedding models — vectors from different models are not comparable, and there is no warning when you mix them.

Testing. Don't hit the API in CI. Two workable approaches:

  • Mock your own service interface and assert on the prompt you would have sent.
  • Point the client at a local fake by overriding base_uri in the test config, and assert on the outgoing payload with Http::fake() / a PSR-18 mock.

Assert on request shape (model, roles, temperature, token ceiling) and on your parsing and error handling, not on exact completion strings — those are non-deterministic.

Config as first-class. Put model names, token ceilings, and temperature in config/services.php or a dedicated config/ai.php, not inline. Model deprecations happen on OpenAI's schedule, not yours; you want a one-line swap under code review.

Gotchas and Tips

^0.19.0 is not a stability guarantee. The whole openai-php/client line is pre-1.0, where semver permits breaking changes in the minor position. ^0.18 || ^0.19 would be a lie. After composer update, actually read the client's 0.18 → 0.19 diff for renamed methods, changed response DTO properties, or shifted constructor signatures. If it's a routine patch, commit the lockfile and move on; if a DTO changed shape, your $response->usage->totalTokens style accessors are the first thing to break.

Transitive version conflicts are the realistic failure mode. Because this bundle constrains openai-php/client, bumping it can force a client upgrade that collides with a second package pinning an older client. Run composer why openai-php/client before and after; if you see two constraints, you'll need --with-dependencies and possibly a temporary -W run to see the real resolution.

Timeouts are the default you'll regret. The client's default HTTP timeout is generous enough that a hung request can outlive your PHP max_execution_time or your queue worker's timeout. Set an explicit timeout and a bounded retry count in config. Retries on 429 are fine; retries on a 400 validation error just burn money three times.

Error handling is exception-based and not granular by default. Catch OpenAI\Exceptions\ErrorException and inspect the message/type rather than the class alone — context-length overflows and rate limits arrive through the same type. Map rate limits to a retry, context overflows to a chunking path, and auth failures to a loud failure.

Watch the token ceiling before you send. Long user input silently truncates or hard-errors depending on the model. Estimate tokens (strlen($text) / 4 is crude but catches the pathological cases) and chunk before the call, not after the exception.

Cost and latency are the two things that surprise teams. Cache aggressively on deterministic prompts (hash the normalised prompt + model + temperature into a cache key with a TTL). Use the small model for classification and routing, the large model only where quality is visibly needed. Log token usage per request from $response->usage so you have a number when someone asks.

Pinning down nondeterminism. temperature: 0 reduces but does not eliminate variation. If you need byte-identical output for a regression test, snapshot your parsing of a recorded fixture response, not a live call.

Extension points worth knowing. The base URI is configurable, which is how you point at Azure OpenAI, a proxy, or a local fake. The HTTP transport is swappable, which is how you add tracing (log request id and latency around the call — the client won't do it for you). And in the Symfony bundle, the service definition is the seam for decorating the client with your own logging or rate-limiting implementation.

On this specific release: nothing to migrate. Treat v0.19.0 as a housekeeping bump, re-run your test suite against the new client, and confirm your recorded fixtures still parse — response DTO shape is the one thing a dependency bump of this kind can quietly change.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle