docs: rewrite JsonRpcBundle docs, add SDK docs, move them under docs/api
en/docs/api/json-rpc-bundle/functionality/events/pre_execute.md
new file +127
−0
rpc.pre_execute
Event name
rpc.pre_execute
Constant
RpcEvent::PRE_EXECUTE
Class
RpcPreExecuteEvent
Properties
RpcRequest $rpcRequest, Service $service, array $params
The most important event: on it the bundle validates and converts parameters, acquires the lock and executes the method itself. Your listener becomes part of this pipeline — exactly where is determined by its priority.
When it is dispatched
Once per method execution: after a cache miss and after the method has been found. For any request source — HTTP, console, batch, Messenger, deferred execution of notifications/callback, cache warm-up.
Not dispatched for a cached response, an unknown method and the receipt of notifications/callback (the method runs later, and the event is dispatched then).
Built-in listeners
| Priority | Listener | What it does |
|---|---|---|
| 1002 | ValidateParamsListener |
Turns parameters into the list of method arguments (named and positional), fills in default values and services from #[Autowire]. Overwrites $event->params. Missing required parameters — rpc.error |
| 1001 | ValidateParamsListener::constraintValidation |
Checks #[RPC\Assertions]; violations — rpc.error |
| 1000 | ParamsConverterEventListener |
Converts scalars into objects and arrays into DTOs |
| -100 | LockEventListener |
If the request already has an error — rpc.error; otherwise acquires #[RPC\Lock] |
| -1000 | RpcServerListener |
Calls the method with $event->params, builds the response (rpc.pre_response), dispatches rpc.post_execute and stops propagation |
What can be changed
$event->params are exactly the arguments the method will be called with. The effect depends on the priority:
| Listener priority | What you see and what you change |
|---|---|
| above 1002 | Raw request parameters; your changes will be overwritten at 1002 |
| between 1002 and 1000 | List of arguments with scalars; changes will go through validation and conversion |
| between 1000 and -1000 | Ready arguments (objects, DTOs); changes are passed to the method as is |
| below -1000 | Called only on the execution error path |
- The cache key is computed from the raw request parameters, so changes to
$paramsdo not affect it. - You should not replace
$event->rpcRequest— it desynchronizes the server.
Stopping propagation skips the method call. Only the error scenario is supported: dispatch rpc.error and stop the event — this is what the built-in listeners do. There is no standard way to return a successful result without calling the method.
public function __construct(private RpcEventFactory $events) {}
// ...
$this->events->fireError($event->rpcRequest, new RpcLogicException('Not allowed'));
$event->stopPropagation();
Usage examples
Parameter value from context
Substitute the current client's ID for the one the client passed (protection against spoofing):
#[AsEventListener(RpcEvent::PRE_EXECUTE, priority: 1001)]
final class TenantParamListener
{
public function __construct(private TenantResolver $tenants) {}
public function __invoke(RpcPreExecuteEvent $event): void
{
if (array_key_exists('tenantId', $event->params)) {
$event->params['tenantId'] = $this->tenants->current();
}
}
}
Priority 1001: after the argument list is built (1002), but before conversion (1000).
Method access check
#[AsEventListener(RpcEvent::PRE_EXECUTE, priority: 999)]
final class AccessListener
{
public function __construct(private RpcEventFactory $events, private Acl $acl) {}
public function __invoke(RpcPreExecuteEvent $event): void
{
if (!$this->acl->canCall($event->service->name)) {
$this->events->fireError($event->rpcRequest, new RpcLogicException('Access denied'));
$event->stopPropagation();
}
}
}
Other ideas
- measuring execution time (start here, finish on
rpc.post_execute/rpc.error); - opening a Doctrine transaction before the method runs;
- change auditing: storing parameters of methods that modify data.