Adobe Sign for Symfonyconceptlz/adobe-sign-symfony GitHub

Reference

Services, events, routes & commands

Everything the Symfony bundle adds, in one place: class names, service ids, tags, event properties, routes, commands, configuration keys and error messages, exactly as they are in the code. The guides explain when to use each piece.

Classes at a glance

All classes live under Conceptlz\AdobeSign\Symfony\.

ClassRole
AdobeSignBundleThe bundle (AbstractBundle, alias adobe_sign): configuration tree and all service definitions. Constant DEFAULT_SCOPES.
AdobeSignManagerBuilds clients; OAuth, token, webhook and signature-level helpers. Implements ResetInterface.
UserKeyResolverInterface: keyFor(UserInterface $user): string.
DefaultUserKeyResolverKeys tokens by getUserIdentifier().
MisconfiguredExceptionA setting a feature needs is missing or invalid. Extends RuntimeException, implements the core's AdobeSignException.
TokenStore\TokenEncryptorlibsodium secretbox for stored tokens: encrypt(AccessToken): string, decrypt(string): ?AccessToken.
TokenStore\CacheTokenStoreCore TokenStore in a PSR-6 pool; adds forget().
TokenStore\DoctrineDbalTokenStoreCore TokenStore in a DBAL table; adds forget(), configureSchema(), createTable(), table().
TokenStore\DoctrineSchemaListenerInternal: adds the token table to the ORM schema on postGenerateSchema.
Controller\OAuthControllerActions redirect() and callback().
Controller\WebhookControllerInvokable webhook endpoint.
Event\OAuthConnectedEventA user finished the OAuth flow.
Event\WebhookReceivedEventA new webhook notification arrived.
Message\AdobeSignWebhookMessageThe Messenger message for a notification.
Command\VerifyCommandadobe-sign:verify.
Command\CreateTokenTableCommandadobe-sign:create-token-table.

Services and aliases

Registered by AdobeSignBundle::loadExtension(). Ids that are class names are autowirable by that type. Inspect them with php bin/console debug:container adobe_sign and debug:container 'Conceptlz\AdobeSign'.

IdClassPublicNotes
Conceptlz\AdobeSign\Symfony\AdobeSignManagersameyesTags: monolog.logger (channel adobe_sign), kernel.reset (method reset).
Conceptlz\AdobeSign\AdobeSigncore clientyesFactory [AdobeSignManager, 'client'], not shared.
Conceptlz\AdobeSign\Auth\TokenStorealiasnoPoints at the configured store.
adobe_sign.token_store.cacheCacheTokenStorenoOnly with token_store: cache. Arguments: oauth.token_cache_pool, TokenEncryptor.
adobe_sign.token_store.doctrineDoctrineDbalTokenStorenoOnly with token_store: doctrine. Arguments: doctrine.dbal.<connection>_connection, TokenEncryptor, table.
Conceptlz\AdobeSign\Symfony\TokenStore\DoctrineDbalTokenStorealiasnoOf adobe_sign.token_store.doctrine, only with token_store: doctrine.
Conceptlz\AdobeSign\Symfony\TokenStore\TokenEncryptorsamenoArgument: oauth.encryption_key.
Conceptlz\AdobeSign\Symfony\UserKeyResolverDefaultUserKeyResolvernoUsed unless oauth.user_key_resolver names another service.
adobe_sign.cacheSymfony\Component\Cache\Psr16CachenoPSR-16 view of cache_pool, given to every client for the data-centre lookup.
Conceptlz\AdobeSign\Symfony\Controller\OAuthControllersameyesTag controller.service_arguments.
Conceptlz\AdobeSign\Symfony\Controller\WebhookControllersameyesTags controller.service_arguments, monolog.logger (channel adobe_sign).
Conceptlz\AdobeSign\Symfony\Command\VerifyCommandsamenoTag console.command.
Conceptlz\AdobeSign\Symfony\Command\CreateTokenTableCommandsamenoOnly with token_store: doctrine.
Conceptlz\AdobeSign\Symfony\TokenStore\DoctrineSchemaListenersamenoOnly with token_store: doctrine and doctrine/orm installed. Tag doctrine.event_listener (postGenerateSchema).

What the manager receives

The resolved configuration, the TokenStore, the UserKeyResolver (or oauth.user_key_resolver), adobe_sign.cache, and these optional services, each passed as null when missing: logger, the http_client service or psr18.http_client, router, security.token_storage and clock.

AdobeSignManager methods

MethodReturnsThrows
client()AdobeSignMisconfiguredException
forUser(UserInterface|string|int $user)AdobeSignMisconfiguredException (OAuth not configured)
isConnected(UserInterface|string|int $user)boolwhatever the store throws
oauth()Conceptlz\AdobeSign\Auth\OAuthClientMisconfiguredException
tokens()Conceptlz\AdobeSign\Auth\TokenStore
webhookHandler()Conceptlz\AdobeSign\Webhook\WebhookHandlerMisconfiguredException
userKey(UserInterface|string|int $user)string
signatureLevel()Conceptlz\AdobeSign\Enum\SignatureLevelMisconfiguredException
qualifiedIssuers()list<string>
signatureShortfalls(string $agreementId, ?SignatureLevel $required = null)list<string>SDK exceptions; InvalidArgumentException for QES without issuers
reset()void

Descriptions and examples: The manager's methods.

Events

Both extend Symfony\Contracts\EventDispatcher\Event and are dispatched under their class name, so #[AsEventListener] on an __invoke(TheEvent $e) method is enough.

OAuthConnectedEvent

PropertyTypeContent
$userSymfony\Component\Security\Core\User\UserInterfaceThe signed-in user who connected.
$keystringToken-store key, from userKey().
$apiAccessPoint?stringThe account's API host, e.g. https://api.eu1.adobesign.com/.

Dispatched by OAuthController::callback() after the token is stored, before the redirect.

WebhookReceivedEvent

PropertyTypeContent
$eventConceptlz\AdobeSign\Webhook\WebhookEventThe typed notification: AgreementEvent, MegaSignEvent, WidgetEvent, LibraryDocumentEvent or UnknownEvent.

Dispatched synchronously by WebhookController for each new notification (duplicates dropped), before the Messenger message.

The Messenger message

final class AdobeSignWebhookMessage

__construct(array $payload), readonly $payload: the decoded notification body.

event(): WebhookEvent rebuilds the typed event; notificationId(): ?string returns webhookNotificationId.

Dispatched on webhooks.messenger.bus after WebhookReceivedEvent, only when webhooks.messenger is enabled, without stamps. See Async with Messenger.

Routes

Import from @AdobeSignBundle/config/routes.php (all) or @AdobeSignBundle/config/routes/oauth.php / routes/webhook.php.

NameMethodsPathControllerNotes
adobe_sign_oauth_redirectGET/adobe-sign/oauth/redirectOAuthController::redirectNeeds a session. Protect with access_control.
adobe_sign_oauth_callbackGET/adobe-sign/oauth/callbackOAuthController::callbackNeeds a session and a signed-in user.
adobe_sign_webhookGET, POST/adobe-sign/webhookWebhookControllerStateless. Must be public: no login, no session.

Responses

RouteStatusWhen
redirect302Always (to Adobe's consent page), unless OAuth is misconfigured (500).
callback302Tokens stored; to oauth.redirect_after.
callback400STATE_MISMATCH, MISSING_CODE, or an untrusted api_access_point.
callback401No signed-in user ("Sign in before connecting Acrobat Sign.").
callback403Adobe returned an error or refused the code.
callback502Adobe unreachable ("Acrobat Sign could not be reached. Please try again.").
webhook200Accepted (including duplicates): JSON {"xAdobeSignClientId": "<id>"} and header X-AdobeSign-ClientId.
webhook400POST body isn't a JSON object with an event.
webhook403Client id header missing or not allowed. Empty body.
webhook500No allow-list (MisconfiguredException), or a listener or the bus threw.

Console commands

adobe-sign:verify

php bin/console adobe-sign:verify [--user=KEY]

Calls GET /users/me with the default client, or with forUser(KEY) when --user is given, and prints the user's email, whether they are an account admin, and the account id. Exit code 0 on success; 1 with the SDK's message on any AdobeSignException (including MisconfiguredException). Always registered.

adobe-sign:create-token-table

php bin/console adobe-sign:create-token-table

Creates the token table on the configured connection if it doesn't exist ("Created table …" or "Table … already exists."). Always exits 0. Registered only with oauth.token_store: doctrine.

Configuration keys

KeyTypeDefault
authscalarintegration_key
integration_keyscalarnull
oauth.client_idscalarnull
oauth.client_secretscalarnull
oauth.redirect_uriscalarnull (route adobe_sign_oauth_callback)
oauth.scopeslistAdobeSignBundle::DEFAULT_SCOPES
oauth.redirect_afterscalar/
oauth.user_key_resolverservice idnull
oauth.token_storecache | doctrine | service idcache
oauth.token_cache_poolservice idcache.app
oauth.encryption_keyscalar%kernel.secret%
oauth.doctrine.connectionscalardefault
oauth.doctrine.tablescalaradobe_sign_tokens
signature_levelscalarses
qualified_issuerslist[]
bootstrap_urlscalarhttps://api.adobesign.com
base_uri_ttlinteger86400
retries.maxinteger3
retries.max_delay_secondsfloat60.0
cache_poolservice idcache.app
http_clientservice idnull
webhooks.client_idslist[] (falls back to oauth.client_id)
webhooks.dedupe_ttlinteger259200
webhooks.messenger.enabledbooleanfalse
webhooks.messenger.busservice idmessenger.default_bus

Effects and examples: Configuration.

Exceptions

MisconfiguredException

The message always names the setting. The complete list:

MessageThrown by
Set adobe_sign.integration_key (usually "%env(ADOBE_SIGN_INTEGRATION_KEY)%"), or adobe_sign.auth: oauth.client()
adobe_sign.auth is "oauth" but no user is signed in; use AdobeSignManager::forUser($user).client()
adobe_sign.auth must be "integration_key" or "oauth"; got "…".client()
Set adobe_sign.oauth.client_id and client_secret to use OAuth.oauth(), so also forUser(), the OAuth routes
Set adobe_sign.oauth.redirect_uri: the router is not available to build it.oauth()
Set adobe_sign.webhooks.client_ids (or adobe_sign.oauth.client_id) to receive webhooks.webhookHandler(), so the webhook route
adobe_sign.signature_level must be ses, aes or qes; got "…".signatureLevel(), and every client creation
adobe_sign.oauth.encryption_key is empty; set it or kernel.secret (APP_SECRET).TokenEncryptor, on first encrypt or decrypt

Core exceptions you will meet

Everything else comes from the core and implements Conceptlz\AdobeSign\Exception\AdobeSignException: ApiException and its subclasses (AuthenticationException, OAuthException, NotFoundException, ValidationException, AccountCapabilityException, PreconditionFailedException, RateLimitException), plus TransportException, InvalidArgumentException and WebhookException. See the core's errors page.

Common questions

Can I decorate or extend AdobeSignManager?

It isn't final, so you can, and Symfony's #[AsDecorator(AdobeSignManager::class)] works. Its constructor is marked internal, though, so prefer wrapping it in your own service over subclassing; the constructor may change between minor versions.

Why can't I autowire CacheTokenStore?

It is registered under adobe_sign.token_store.cache without a class alias. Autowire Conceptlz\AdobeSign\Auth\TokenStore, or call $manager->tokens() and check instanceof CacheTokenStore when you need forget().

In which order do listeners for WebhookReceivedEvent run?

By priority, like any Symfony listener: #[AsEventListener(priority: 10)] runs before the default 0. stopPropagation() stops later listeners but not the Messenger message, which is dispatched afterwards regardless.

Can I point my own routes at the bundle's controllers?

Yes. The controllers are public services tagged controller.service_arguments; define a route with controller: Conceptlz\AdobeSign\Symfony\Controller\WebhookController (or OAuthController::callback). Keep the webhook route stateless and the default redirect URI in mind: it is generated from adobe_sign_oauth_callback, so set oauth.redirect_uri if you rename the callback.