rpc.pre_execute

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 $params do 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.