rpc.pre_execute

← rpc.pre_execute

Ashterix

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

1 + # rpc.pre_execute
2 +
3 + ::::::block class="floatinginfobox floatinginfobox--fit"
4 + :::::columns border zebra
5 + ::::row
6 + :::col width="40%"
7 + **Event name**
8 + :::
9 + :::col
10 + `rpc.pre_execute`
11 + :::
12 + ::::
13 + ::::row
14 + :::col width="40%"
15 + **Constant**
16 + :::
17 + :::col
18 + `RpcEvent::PRE_EXECUTE`
19 + :::
20 + ::::
21 + ::::row
22 + :::col width="40%"
23 + **Class**
24 + :::
25 + :::col
26 + `RpcPreExecuteEvent`
27 + :::
28 + ::::
29 + ::::row
30 + :::col width="40%"
31 + **Properties**
32 + :::
33 + :::col
34 + `RpcRequest $rpcRequest`, `Service $service`, `array $params`
35 + :::
36 + ::::
37 + :::::
38 + ::::::
39 +
40 + 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.
41 +
42 + ## When it is dispatched
43 +
44 + 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.
45 +
46 + **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).
47 +
48 + ## Built-in listeners
49 +
50 + | Priority | Listener | What it does |
51 + | --- | --- | --- |
52 + | 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` |
53 + | 1001 | `ValidateParamsListener::constraintValidation` | Checks [`#[RPC\Assertions]`](/docs/api/json-rpc-bundle/add_rpc_service/assertions); violations — `rpc.error` |
54 + | 1000 | `ParamsConverterEventListener` | [Converts](/docs/api/json-rpc-bundle/add_rpc_service/params) scalars into objects and arrays into DTOs |
55 + | -100 | `LockEventListener` | If the request already has an error — `rpc.error`; otherwise acquires [`#[RPC\Lock]`](/docs/api/json-rpc-bundle/add_rpc_service/lock) |
56 + | -1000 | `RpcServerListener` | Calls the method with `$event->params`, builds the response (`rpc.pre_response`), dispatches `rpc.post_execute` and stops propagation |
57 +
58 + ## What can be changed
59 +
60 + **`$event->params` are exactly the arguments the method will be called with.** The effect depends on the priority:
61 +
62 + | Listener priority | What you see and what you change |
63 + | --- | --- |
64 + | above 1002 | Raw request parameters; your changes will be overwritten at 1002 |
65 + | between 1002 and 1000 | List of arguments with scalars; changes will go through validation and conversion |
66 + | between 1000 and -1000 | Ready arguments (objects, DTOs); changes are passed to the method as is |
67 + | below -1000 | Called only on the execution error path |
68 +
69 + * The cache key is computed from the raw request parameters, so changes to `$params` do not affect it.
70 + * You should not replace `$event->rpcRequest` — it desynchronizes the server.
71 +
72 + **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.
73 +
74 + ```php
75 + public function __construct(private RpcEventFactory $events) {}
76 +
77 + // ...
78 + $this->events->fireError($event->rpcRequest, new RpcLogicException('Not allowed'));
79 + $event->stopPropagation();
80 + ```
81 +
82 + ## Usage examples
83 +
84 + ### Parameter value from context
85 +
86 + Substitute the current client's ID for the one the client passed (protection against spoofing):
87 +
88 + ```php
89 + #[AsEventListener(RpcEvent::PRE_EXECUTE, priority: 1001)]
90 + final class TenantParamListener
91 + {
92 + public function __construct(private TenantResolver $tenants) {}
93 +
94 + public function __invoke(RpcPreExecuteEvent $event): void
95 + {
96 + if (array_key_exists('tenantId', $event->params)) {
97 + $event->params['tenantId'] = $this->tenants->current();
98 + }
99 + }
100 + }
101 + ```
102 +
103 + Priority 1001: after the argument list is built (1002), but before conversion (1000).
104 +
105 + ### Method access check
106 +
107 + ```php
108 + #[AsEventListener(RpcEvent::PRE_EXECUTE, priority: 999)]
109 + final class AccessListener
110 + {
111 + public function __construct(private RpcEventFactory $events, private Acl $acl) {}
112 +
113 + public function __invoke(RpcPreExecuteEvent $event): void
114 + {
115 + if (!$this->acl->canCall($event->service->name)) {
116 + $this->events->fireError($event->rpcRequest, new RpcLogicException('Access denied'));
117 + $event->stopPropagation();
118 + }
119 + }
120 + }
121 + ```
122 +
123 + ### Other ideas
124 +
125 + * measuring execution time (start here, finish on `rpc.post_execute` / `rpc.error`);
126 + * opening a Doctrine transaction before the method runs;
127 + * change auditing: storing parameters of methods that modify data.