msales/grapes-bundle
msales/grapes-bundle integrates the Grapes GraphQL library into Symfony applications, offering service container wiring, schema and resolver configuration, and streamlined query execution for PHP APIs. It helps teams build maintainable GraphQL endpoints with familiar Symfony conventions.
Know what you're installing first. msales/grapes-bundle is a Symfony bundle that wires the msales/grapes API-client library into a Symfony container. There is no Laravel service provider, no facade, no publishable config, and no auto-discovery. For a Laravel codebase this means the bundle itself provides almost nothing — its entire value is a handful of DI service definitions and parameter mappings you can read in a few minutes. The latest release, 1.1.0, added Symfony 4 support (previously the bundle targeted Symfony 3 only). That is a compatibility bump, not a modernization: PHP constraints are still 7.x-era, and there is still no Laravel integration.
Minimal adoption path:
composer require msales/grapes-bundle --dry-run and composer why-not msales/grapes-bundle on your current stack. On PHP 8.1+ / Laravel 9+ expect a hard block. Symfony 4 support does not help here — Symfony 4 is itself EOL and incompatible with modern PHP.Extension, and Resources/config/services.* — in that order. Those three files are the whole package. Whatever the bundle registers is what you will be re-registering.msales/grapes README for the actual runtime behavior (client, request, response types). The bundle contributes config, not logic.GrapesServiceProvider, bind the grapes client interface as a singleton, inject it into one gateway class, and make a single GET call to a known endpoint from php artisan tinker.If you are starting fresh, stop here and use Laravel's Http client or Guzzle/Saloon instead. Only proceed if you have existing code that already speaks msales/grapes types and you need the container wiring to match.
Port the config, don't depend on the bundle. Extract the bundle's parameters into config/services.php (or a dedicated config/grapes.php), and translate each Symfony service definition into a Laravel binding:
// app/Providers/GrapesServiceProvider.php
public function register(): void
{
$this->app->singleton(GrapesClient::class, function ($app) {
return new GrapesClient(
baseUri: config('services.grapes.base_uri'),
timeout: config('services.grapes.timeout', 5),
);
});
}
Keep env() calls inside config files only, exactly as you would for any other Laravel service.
Hide the dependency behind your own interface. Given 4 stars, zero dependents, and a maintenance history that consists of a single Symfony 4 compatibility bump years after the fact, do not let grapes types leak into controllers, jobs, or domain code. Define App\Contracts\InventoryClient (or similar), implement it with a GrapesInventoryClient adapter, and bind the interface. Swapping to Http:: later becomes a one-file change.
Keep HTTP work in gateway classes. One class per remote service, constructor-injected client, methods that return your own DTOs (or arrays), and exceptions normalized to Laravel-friendly ones. Controllers and jobs call the gateway; nothing else knows grapes exists.
Queue workers: build per job, fail loudly. Old client libraries frequently lack retry/backoff. Set explicit timeouts in the binding, catch transport exceptions in the gateway, and let the job's $tries / $backoff handle retries rather than relying on the library. Do not reuse a client across long-lived worker iterations if it caches state.
Testing pattern. Because you hid the client behind an interface, most tests bind a fake and never touch grapes:
$this->app->instance(InventoryClient::class, new FakeInventoryClient());
For the adapter's own test, inject a Guzzle/PSR-18 mock handler into the underlying grapes client, or (simpler) wrap any PSR-18 call in Laravel's Http client so Http::fake() works with zero extra setup.
Integration tip — adapter, not migration. If only one or two endpoints still use grapes, add a GrapesAdapter next to your newer Http-based clients. Both satisfy the same interface, so you can migrate endpoint by endpoint and delete the provider when the last caller is gone.
extra.laravel.providers, no config/ to publish, no Artisan commands. Any guide that says "install and configure" assumes Symfony Flex and will not apply.symfony/* constraints and modern Laravel lives on Symfony 6/7. A minor release that adds support for an already-EOL Symfony major is not a step toward Laravel compatibility. --ignore-platform-reqs may technically install it and leave you with a container that breaks at runtime. Treat an install failure as a signal, not an obstacle.composer.json, commit the lock file, and consider vendoring the two or three classes you actually use into app/Support instead of carrying the dependency.app(GrapesClient::class) in tinker; (2) is the base URI/timeout actually populated? dump the config; (3) is the failure a transport error or a DI error? Nine times out of ten it is wiring, not the library, because the library surface you use is tiny.Http is untestable and impossible to reason about during an incident. Pick one per adapter.How can I help you explore Laravel packages today?