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.