userbase/client-bundle
Symfony bundle integrating the Userbase PHP client. Register and configure the client through bundle configuration, then inject it as a service to add user authentication, sessions, and encrypted per-user data storage to your Symfony application with minimal setup.
Start by installing the bundle and inspecting exactly what it registers, because there is effectively no public documentation or community footprint to lean on.
composer require userbase/client-bundle
On a modern Symfony app, Flex will usually register the bundle automatically. If not, add it to config/bundles.php:
return [
// ...
Userbase\ClientBundle\UserbaseClientBundle::class => ['all' => true],
];
Then create a config file. The exact root key is defined by the bundle's DependencyInjection/Configuration.php, so confirm it before guessing — it is typically userbase_client (or userbase). A config that mirrors the usual client-service shape looks like:
# config/packages/userbase_client.yaml
userbase_client:
base_uri: '%env(USERBASE_BASE_URI)%'
api_key: '%env(USERBASE_API_KEY)%'
timeout: 5
USERBASE_BASE_URI=https://userbase.example.com
USERBASE_API_KEY=change-me
Where to look first (in this order):
README.md — even thin bundles usually show the service ID and a usage snippet.src/DependencyInjection/Configuration.php — the authoritative list of config keys and defaults.src/Resources/config/services.{yaml,xml} — the actual service IDs, class names, and whether they are public.src/ client/service classes — the public methods you can call and the DTOs/arrays they return.First use case: fetch an identity from UserBase and render it. Wire the client into a controller via constructor injection (use the class name from services.yaml, not a string service ID):
public function __construct(private UserbaseClient $client) {}
#[Route('/me', name: 'me')]
public function me(): Response
{
$user = $this->client->getUser($this->getUser()->getUserIdentifier());
return $this->render('me.html.twig', ['user' => $user]);
}
Verify the wiring before writing more code:
bin/console debug:config userbase_client # confirm keys are loaded
bin/console debug:container --show-private | grep -i userbase
Wrap the client in an application-level adapter. Treat the bundle's client as an infrastructure detail. Define your own interface (App\Userbase\UserDirectory) and implement it with the bundle service. When the bundle's API changes — likely, given its maturity — you only touch one class.
final class UserbaseDirectory implements UserDirectory
{
public function __construct(private UserbaseClient $client) {}
public function find(string $id): ?User
{
try {
return $this->client->getUser($id);
} catch (NotFoundHttpException) {
return null;
}
}
}
Prefer constructor injection and autowiring aliases. If the bundle registers multiple services (a client, a factory, a token store), bind interfaces in config/services.yaml:
services:
UserbaseClient: '@userbase.client'
Cache reads aggressively. A remote identity service is a latency and availability dependency. Wrap read calls in Symfony Cache and invalidate on write:
return $this->cache->get('ub.user.'.$id, function (ItemInterface $item) use ($id) {
$item->expiresAfter(300);
return $this->client->getUser($id);
});
Handle writes as explicit, idempotent operations. If the bundle exposes create/update/delete, put them behind your adapter and log the request payload. Remote writes without local audit trails are painful to debug later.
Integrate with Symfony Security via a custom user provider. If the bundle does not ship a provider, write one that delegates to your adapter. Keep the API call out of the hot path by caching the resolved user in the token/session:
public function loadUserByIdentifier(string $identifier): UserInterface
{
$remote = $this->directory->find($identifier);
if (!$remote) {
throw new UserNotFoundException();
}
return (new SecurityUser($remote->id))->setRoles($remote->roles);
}
Use Symfony HttpClient features rather than bolting them on. If the bundle lets you inject an HttpClientInterface, supply a scoped client with retry and timeout configured:
framework:
http_client:
scoped_clients:
userbase.client:
base_uri: '%env(USERBASE_BASE_URI)%'
timeout: 5
retry_failed:
max_retries: 2
Add a console command for sync and smoke tests. A userbase:ping or userbase:sync command gives you a fast feedback loop that does not require booting a full HTTP request, and it doubles as a deployment health check.
Test with MockHttpClient. Because the bundle sits on top of HttpClient, you can assert outgoing requests and simulate error paths without hitting a real service — far better than mocking the bundle's concrete client class.
"userbase/client-bundle": "1.0.*" or a commit hash) and read the source before upgrading.debug:config after every config change.$container->get(). If you are porting old code, use debug:container to check visibility and prefer rewriting the call site to constructor injection rather than adding public: true.%userbase.api_key% style parameters rather than %env(...)%, env vars may not be picked up in cache:warmup/prod. Check whether the parameter or the env var is authoritative.HttpClient often inherit a null timeout. One slow upstream call will hang PHP-FPM workers. Override with a scoped client or the bundle's timeout config if it exists.null field or a changed key does not leak into templates.debug:container --tag=... | grep userbase, then check var/log/dev.log for the bundle's channel. Add a Monolog channel for the client namespace and log request IDs if the service returns them.decorates: userbase.client), override bundle templates under templates/bundles/, and use a CompilerPass to swap the HTTP client. Forking a 0-star bundle should be the last resort — vendor the one or two classes you need instead.docs/userbase.md recording the pinned version, the endpoints used, and the config keys — future you will not remember which fields the API returned.How can I help you explore Laravel packages today?