google-gemini-php/symfony
Symfony integration for Google Gemini PHP, making it easy to configure the Gemini API and use text, chat, and vision features via Symfony services. Lightweight, framework-friendly setup for AI-powered apps.
Architecture fit. Unchanged in shape and still the right shape: a Bundle class, a DI extension, a configuration tree, and service aliases. Two things in 2.1.0 improve the fit. First, the client can now be autowired by its interface, Gemini\Contracts\ClientContract, rather than a concrete class — that gives us a proper seam for decoration, aliasing, and test doubles without touching application code. Second, base_url is now a recognized option in config/packages/gemini.yaml; previously the extension applied it but the configuration tree did not declare it, so setting it failed with "Unrecognized option." That fix is the difference between "we can point this at a regional endpoint, gateway, or mock server" and "we cannot." The bundle still contributes no runtime layer of its own and imposes no abstraction on domain code.
Integration feasibility. Lower than before. ClientFake (surfaced in the refreshed README, shipped via client 2.8.0) means we can unit-test call sites against a fake rather than stubbing PSR-18 or standing up a fake HTTP server — that removes the single largest piece of test scaffolding from the original estimate. Symfony 8 is now supported, and the README is refreshed with current models and configuration examples, which shortens the "read the source to figure out the config keys" phase. Caveats: the client bump requires PHP 8.2, and the fake is a client-level feature — if we stay on an older pinned client, we do not get it. Still a sub-day spike for a competent Symfony developer; the real work remains prompt versioning, response validation, and where calls live in the codebase.
Technical risk. The stewardship question moved in the right direction but did not clear. This is PR #2 against the repository, by a single contributor, with the README refresh bundled into the same change — evidence of a live maintainer, not evidence of a cadence, a review process, or a security policy. Star and dependent counts remain negligible, so there is still no production track record we can point to. Two new, specific risks: (a) declaring Symfony 8 support may widen the constraint set in ways that conflict with our lockfile, so resolution must be verified rather than assumed; (b) version skew between bundle 2.1.0 and client 2.8.0 — the testing story and the newer client features are contingent on the client version, and the bundle's composer.json constraint is what decides it for us. The release-notes link to the client changelog is the authoritative source for what 2.8.0 actually adds; we should read it rather than infer from the summary. The earlier concern about the anomalous release date is partly mitigated by the presence of a real compare link (2.0.0...2.1.0) — this is a genuine, if small, release — but tag history should still be confirmed.
Key questions for the maintainer / our spike (carried forward, with status).
Gemini\Contracts\ClientContract.ClientFake is documented; confirm it covers streaming and error paths, not just happy-path completions.base_url configuration — now declared and settable.base_url bug suggests the config tree is still being hardened; verify whether a missing API key is caught at container compile time or at first call, since that determines whether CI builds without a key break.symfony/http-client without php-http/discovery configuration.ClientFake can simulate it.Position. Approve for a time-boxed pilot behind an internal adapter interface. The 2.1.0 changes make the pilot cheaper and the testing story much better, but they do not change the underlying governance posture. Do not let ClientContract or the vendor client type leak into controllers or domain services — autowiring by interface makes that discipline easier, not optional.
Stack fit. Unchanged baseline (Symfony 6.4/7.x, PHP 8.2+), now with a forward path to Symfony 8. Still composes cleanly with symfony/http-client for transport, Monolog for redacted audit logging, Redis for per-tenant rate limiting, and Messenger for off-request generation. base_url being configurable is the enabler for pointing at a corporate egress proxy, a regional endpoint, or a local stub in integration tests — previously that required code-level overrides.
Migration path. Same two scenarios, with one refinement. (1) Greenfield: install directly, but wrap the injected client in our own TextGenerationInterface with one adapter implementation; type-hint ClientContract only inside the adapter. (2) Existing raw cURL/Guzzle calls: land the adapter in front of current code first, ship unchanged, then swap the adapter body to the bundle in a follow-up PR. If we are on the Laravel sibling package, the port is mostly namespace and service-container changes. In both cases, 2.1.0 is a stronger starting point than 2.0.0 because the adapter's constructor can now depend on an interface and its tests can use ClientFake rather than hand-rolled HTTP stubs.
Compatibility. Three checks before committing. First, resolve the bundle against our lockfile — confirm it accepts our Symfony and PHP versions and does not pin a client older than 2.8.0, which would silently cost us ClientFake and the newer client features. Second, confirm Flex recipe availability; if absent, config/config/bundles.php registration and the config file are manual and must be captured in scaffold docs. Third, verify the config tree does not hard-require an API key at compile time in CI, or builds will fail wherever the key is intentionally absent — the base_url fix shows the config tree is still maturing, so test the negative case explicitly.
Sequencing.
base_url override against a stub.ClientFake plus fixture-based tests for streaming and error paths.Maintenance. Still a dependency we own. Pin the version, watch both the bundle and google-gemini-php/client release feeds, and keep the quarterly half-day for changelog review and contract-test rerun. Two new maintenance notes: because the bundle delegates feature surface to the client, a composer update of the client is a behavior change and should be treated as one; and with Symfony 8 support landing, expect our Symfony upgrade roadmap to pull the bundle forward — sequence that deliberately rather than as a side effect of a composer update. The base_url defect is a reminder to re-run the config-path smoke test after every bump, not just the happy-path generation test.
Support. Community-only, unchanged. No SLA, no escalation path, GitHub issues with unknown response times, one visible contributor. Acceptable for internal tooling; not acceptable for a customer-facing path with hard uptime commitments absent a documented fallback to direct REST calls. Name an internal owner for the adapter, and note that a one-contributor project means that owner is effectively the maintainer of last resort.
Scaling. Unchanged: the client is synchronous HTTP, throughput bounded by Gemini API latency and quota rather than PHP. Plan explicit connect/read timeouts, bounded retries with backoff on 5xx and 429, Messenger queueing for anything over a few seconds, per-tenant concurrency caps, and token accounting. If 2.8.0 introduces streaming support worth using, budget separate timeout and disconnect handling — buffered assumptions break there — and confirm ClientFake can simulate partial streams before relying on it in tests.
Failure modes to design for. Missing or rotated API key surfacing as a compile-time config error or a runtime 401; a mistyped or unrecognized config key failing at boot (the class of bug 2.1.0 just fixed — assume more exist); upstream 5xx and quota exhaustion appearing as unhandled exceptions rather than degradable states; safety-filter blocks returning empty candidate lists that naive code treats as success; token-limit rejections on long prompts; silent model deprecations after a Google-side change; and version skew where the bundle permits a client older than the one the README documents, so a copy-pasted ClientFake example fails to resolve. Each needs a defined product behavior — retry, fallback model, or user-facing message — decided before launch.
Ramp-up. Effectively unchanged and slightly shorter: a Symfony developer is productive in under a day, and the refreshed README with autowiring and ClientFake examples removes most of the discovery work. The non-obvious cost remains governance, not code: data-handling review for prompts leaving our boundary, secrets management rather than .env in production, and prompt/response logging that does not retain sensitive content. That review is still the longer pole, and it does not get shorter because the package improved.
How can I help you explore Laravel packages today?