Lifecycle events

Lifecycle events

Processing of every RPC request goes through Symfony EventDispatcher events. By subscribing to them, you can add auditing, metrics and tracing, change method arguments or replace an error — without changing the procedures.

Listeners interfere with the server's work. Before changing data or stopping event propagation, read the page of the corresponding event: each one describes what is safe to change and what is not.

Event order

For a regular request with an id:

rpc.request
  └─ (cache: if #[RPC\Cache] has a response — nothing else is dispatched)
rpc.pre_execute        ← validation, parameter conversion, #[RPC\Lock], method call
  ├─ success: rpc.pre_response → rpc.post_execute
  └─ error:   rpc.error → rpc.pre_response
── response sent to the client ──
rpc.post_response      ← CacheRelation, notifications, $rpc.callback

For notifications and $rpc.callback, only rpc.request and rpc.pre_response with a placeholder response are dispatched on receipt, and the method itself runs in rpc.post_response — with its own rpc.pre_execute, rpc.pre_response, rpc.post_execute. Full description — on the Lifecycle page.

Event list

Names are constants of Ufo\JsonRpcBundle\EventDrivenModel\Events\RpcEvent, classes are in the same namespace.

Event Class When
rpc.request RpcRequestEvent Request received, before the cache check
rpc.pre_execute RpcPreExecuteEvent Before the method call; the method itself also runs here
rpc.post_execute RpcPostExecuteEvent Method executed successfully
rpc.pre_response RpcPreResponseEvent Response built
rpc.post_response RpcPostResponseEvent Response sent to the client
rpc.error RpcErrorEvent Error during execution
rpc.async_output RpcAsyncOutputEvent A batch child process returned a result
rpc.async_request RpcAsyncRequestEvent Declared, but the bundle does not dispatch it

Subscribing

use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Ufo\JsonRpcBundle\EventDrivenModel\Events\RpcEvent;
use Ufo\JsonRpcBundle\EventDrivenModel\Events\RpcPostResponseEvent;

#[AsEventListener(RpcEvent::POST_RESPONSE)]
final class AuditListener
{
    public function __invoke(RpcPostResponseEvent $event): void
    {
        // $event->rpcRequest->getMethod(), $event->response ...
    }
}

The priority is set in the attribute: #[AsEventListener(RpcEvent::PRE_EXECUTE, priority: 500)]. Higher priority runs earlier. The priorities of built-in listeners are listed on each event's page: they determine what your listener sees and whether it is called at all.

General rules

  • Event properties are public and not readonly, but assigning a new value does not always take effect — see the event's page.
  • stopPropagation() stops not only your neighbors but also built-in listeners with a lower priority. On rpc.pre_execute, rpc.pre_response and rpc.error this can leave the request without a response — do not stop these events unless you build the response yourself.
  • An exception from a listener is turned into an error response. In a single HTTP request it does not go through rpc.error (see Open issues).
  • A cached response (#[RPC\Cache]) is returned without execution events: nothing is dispatched after rpc.request.

Context inside a listener

What you need Where to get it
Method, parameters, id $event->rpcRequest->getMethod(), getParams() (without $rpc), getId()
Special $rpc parameters $event->rpcRequest->getRpcParams() — getTimeout(), hasCallback(), getRayId(), ignoreCache
Notification or not $event->rpcRequest instanceof Ufo\RpcObject\RpcNotificationRequest
API version $event->rpcRequest->apiVersion
API group service Ufo\JsonRpcBundle\Server\ApiGroupContext: getGroup()
HTTP request service Ufo\JsonRpcBundle\Server\RequestPrepare\RequestCarrier: getCarrier(), if it is IRpcHttpCarrier — getHttpRequest()
Token service IRpcSecurity: getTokenHolder()->getToken()
Earlier events of this request service RpcEventFactory: getEvent(Class::class), getEvents(Class::class)

Custom events

RpcEventFactory::fire() accepts any name. An unknown name is dispatched as rpc.custom_event (class RpcEvent with an $eventData array); the bundle itself neither dispatches nor listens to such events.

Migrating from older versions

  • Since 8.0, event classes live in Ufo\JsonRpcBundle\EventDrivenModel\Events\* (previously — Ufo\RpcObject\Events\*).
  • The rpc.response event was removed in 7.6 — use rpc.pre_response or rpc.post_response.
  • Since 10.3, the bundle does not stop propagation of kernel.terminate / console.terminate, so the application's terminate listeners also run after RPC requests.