enm/json-api-client-bundle
Symfony integration for enm/json-api-client that lets you configure JSON:API clients in YAML and exposes each as a private service named enm.json_api_client.YOUR_CLIENT_NAME. Works with any PSR-18 HTTP client, such as Buzz with Nyholm PSR-7.
This bundle is a thin Symfony DI wrapper around enm/json-api-client. Its only real job is to turn YAML client definitions into configured, injectable client services — so treat it as glue, not as a full-stack JSON:API toolkit.
Minimal setup:
composer require enm/json-api-client-bundle
# pick a PSR-18 client + PSR-7 factory (recommended by the README)
composer require kriswallsmith/buzz nyholm/psr7
Register the bundle in config/bundles.php:
return [
// ...
Enm\Bundle\JsonApi\Client\EnmJsonApiClientBundle::class => ['all' => true],
];
Declare at least one client in config/packages/enm_json_api_client.yaml (the bundle throws if clients is empty):
enm_json_api_client:
clients:
api: 'http://example.com/api'
Where to look first:
enm.json_api_client.api — the generated private service. Every configured key under clients becomes a service named enm.json_api_client.<name>.enm/json-api-client docs — the bundle adds almost no API surface; nearly all behavior (resources, relationships, errors) lives upstream.First use case: fetch a single resource and map it to a typed object. Inject the client into a service or controller action, call the client's request method for the resource type, and inspect the resulting document/resource object. Because the service is private, you must inject it — $container->get('enm.json_api_client.api') will fail.
final class ArticleFetcher
{
public function __construct(
private \Enm\JsonApi\Client\JsonApiClient $client,
) {}
}
…and wire the private service to that argument in config/services.yaml (see next section).
Alias the generated client to a concrete type. Because the bundle only registers a private, string-named service, the idiomatic pattern is to alias it to an autowirable class name and — where you have multiple clients — use a named binding:
# config/services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
Enm\JsonApi\Client\JsonApiClient:
alias: enm.json_api_client.api
# multiple clients: bind by argument name
App\Api\CmsClient:
arguments:
$client: '@enm.json_api_client.cms'
Multi-client / multi-environment setups. Since the YAML key is the service suffix, use environment-specific config to point the same client name at different base URLs:
# config/packages/dev/enm_json_api_client.yaml
enm_json_api_client:
clients:
api: '%env(API_BASE_URI)%'
Keep a single logical name (api) per environment and swap only the URL. This avoids conditional service IDs in your code.
Wrap the client in a domain repository. The raw client is request-oriented; encapsulate your own methods so callers don't leak JSON:API vocabulary into application code:
final class ArticleRepository
{
public function __construct(private JsonApiClient $client) {}
public function get(string $id): Article
{
// Build the request for type=articles, id=$id, then map the
// returned ResourceObject onto your Article DTO.
}
}
Reuse a single HTTP stack. Register one PSR-18 client and one PSR-7 factory as services and let Symfony autowire them into the bundle's client factory. This gives you centralized timeout, retry, and logging middleware:
services:
Psr\Http\Client\ClientInterface: '@Http\Client\Common\PluginClient'
Psr\Http\Message\ResponseFactoryInterface: '@Nyholm\Psr7\Factory\Psr17Factory'
Add a logging plugin to the PSR-18 client rather than trying to log inside the bundle — it has no Symfony HTTP-client integration.
Testing. Because the bundle just injects a PSR-18 client, tests should replace the underlying HTTP client with a mock/stub PSR-18 implementation. Don't mock the bundle service; mock one layer lower so you exercise the actual JSON:API request building.
enm/json-api-client before long-term adoption, and consider inlining the tiny Extension class yourself if you only need the DI wiring — it's a handful of lines and removes the abandoned dependency.clients is a hard error. At least one key is required; there's no "no config" fallback. If you enable the bundle in an environment with no clients, the container build fails.enm.json_api_client.<key> literally — a client named api.v2 yields a dotted, awkward-looking ID, and the key is case-sensitive. Stick to snake_case keys.LoggerMiddleware) instead.composer require. Install both explicitly.errors[]) are surfaced by enm/json-api-client's exception types, not by the bundle. Catch the upstream exception and map it to your domain exceptions in a single place.services.yaml decorator around the generated client or, better, the PSR-18 client itself (retries, auth headers, base-URI rewriting).enm/json-api-client version explicitly in composer.json to avoid a silent major-version bump changing deserialization behavior under you.http://example.com/api vs http://example.com/api/ can produce different relative request paths depending on the upstream URI resolver — keep it consistent across environments and add an integration test asserting the outbound URL.How can I help you explore Laravel packages today?