spomky-labs/oauth2-server-client-manager-supervisor
Integrates a client manager supervisor with spomky-labs/oauth2-server, helping PHP OAuth2 servers coordinate client management and validation through a configurable supervisor layer. Useful for wiring client storage and lookup into OAuth2 authorization flows.
This package is part of the older Spomky-Labs OAuth2 framework (pre-League oauth2-server), so treat it as a decorator around a ClientManagerInterface, not a standalone component. The workflow is:
spomky-labs/oauth2-server, the client manager implementation, and an event dispatcher — typically symfony/event-dispatcher or league/event, since Spomky's supervisor layer is event driven).
composer require spomky-labs/oauth2-server-client-manager-supervisor
ClientManagerSupervisor first — it is the only class worth understanding. It implements ClientManagerInterface and delegates every method (getClient, createClient, updateClient, deleteClient, hasClient, etc.) to an injected inner manager while emitting events around the call.ClientManager binding so that every time an OAuth client is created or rotated (API key regeneration, secret rotation, tenant onboarding), you emit a Laravel event or write to an audit table.Minimal wiring:
$this->app->singleton(ClientManagerInterface::class, function ($app) {
return new ClientManagerSupervisor(
new YourClientManager($app['db']->connection()),
$app['events'] // must satisfy the interface the supervisor expects
);
});
Decorator chaining. The supervisor is intentionally a transparent proxy. If you already have caching or throttling managers, stack them: Supervisor(CacheManager(SqlClientManager)). Order matters — put the supervisor outermost if you want events to fire on cache misses as well as DB hits, innermost if you only want events on true persistence operations.
Event-to-Laravel bridge. Spomky's dispatcher contract is not Laravel's Illuminate\Contracts\Events\Dispatcher. Use a small adapter class that forwards dispatch() to Laravel's dispatcher, then register listeners in a EventServiceProvider. Typical listeners:
ClientCreated → invalidate config cache, ping the tenant's webhook URL.ClientDeleted → cascade revoke of issued tokens (do this in a queued job, not synchronously).ClientUpdated → clear an OPcache/Redis entry keyed by client_id if you front the manager with a cache.Repository vs. supervisor boundaries. Keep your manager concerned only with storage. Every cross-cutting concern — audit, notifications, cache invalidation, metrics — goes into a supervisor or a listener on a supervisor event. This keeps the storage layer swappable (SQL in prod, in-memory in tests).
Testing. Because the supervisor is just a decorator, you can instantiate it in a unit test with a fake inner manager and an in-memory dispatcher (Spomky ships a simple one) and assert which events fire for each CRUD call. Do not spin up the full container for this.
Rotation workflow. For secret rotation endpoints, call updateClient on the supervisor-wrapped manager so listeners can log the rotation actor and timestamp. Bypassing the supervisor (e.g. writing directly to the oauth_clients table in a migration) will silently skip every hook — this is the most common integration mistake.
league/oauth2-server with laravel/passport — Passport already emits its own client events (Laravel\Passport\Events\ClientCreated etc.) with better framework integration. Only adopt this package if you are maintaining an existing Spomky codebase.ClientManagerInterface. If you bump the OAuth2 server core but not the supervisor, you will get fatal class must implement method errors when the interface gained a signature. Pin the whole Spomky stack together in composer.json and bump as a unit.ClientManagerInterface is the single extension point — everything else in your app should resolve the interface, never the supervisor class directly.EventDispatcher and re-emit into Laravel events from there.Event::fake() in tests to confirm which events actually fire. If Event::fake() shows nothing, the likely culprit is a direct DB call bypassing the supervisor, or the manager being resolved from a stale container binding after config:cache.LogicException: Serialization of 'Closure' is not allowed. Move the supervisor construction into a singleton call that reads config values, not closures captured at boot.LoggingClientManager and place it inside the supervisor if you want its logs to carry the same request ID as the events, or outside if you want only user-facing events logged.How can I help you explore Laravel packages today?