Вікі-код для #[RPC\Response]

Версія 2.1 додана 2024/05/10 14:32 автором Ashterix

Показати останніх авторів
1 {{box cssClass="floatinginfobox" width="400px"}}
2 == (% style="display:block; margin-top:-30px; text-align:center" %)Summary(%%) ==
3
4 (% style="margin-right:auto; width:353px" %)
5 |(% style="width:141px" %)**Classname**|(% colspan="2" rowspan="1" style="width:79px" %)(((
6 Response
7 )))
8 |(% style="width:141px" %)**Namespace**|(% colspan="2" style="width:79px" %)(((
9 Ufo\RpcObject\RPC
10 )))
11 |(% style="width:141px" %)(((
12 **Target**
13 )))|(% colspan="2" rowspan="1" style="width:79px" %)method
14 |(% colspan="3" style="width:141px" %)**Arguments:**
15 |(% colspan="1" rowspan="3" style="width:141px" %)(((
16 $responseFormat
17 )))|(% style="width:79px" %)**type**|(% style="width:153px" %)array
18 |(% style="width:79px" %)**optional**|(% style="width:153px" %)true
19 |(% style="width:79px" %)**default**|(% style="width:153px" %)[]
20 |(% colspan="1" rowspan="3" style="width:141px" %)(((
21 $dto
22 )))|(% style="width:79px" %)**type**|(% style="width:153px" %)string
23 |(% style="width:79px" %)**optional**|(% style="width:153px" %)true
24 |(% style="width:79px" %)**default**|(% style="width:153px" %)''
25 |(% colspan="1" rowspan="3" style="width:141px" %)(((
26 $collection
27 )))|(% style="width:79px" %)**type**|(% style="width:153px" %)bool
28 |(% style="width:79px" %)**optional**|(% style="width:153px" %)true
29 |(% style="width:79px" %)**default**|(% style="width:153px" %)false
30 {{/box}}
31
32 = Розширений формат відповіді =
33
34 За замовченням документація щодо відповіді кожного сервісу API базується на вказівці {{code language="none"}}return type{{/code}}, що добре працює з примітивними типами, проте в разі якщо ваш метод повертає асоціативний масив, обʼєкт або колекцію обʼєктів інформації про тип даних відповіді стає недостатньо.
35
36 Щоб надати клієнту більше інформації про формат більш складних відповідей був створений атрибут {{code language="none"}}#[RPC\Response]{{/code}}.
37
38 Розглянемо приклад який продемонструє проблематику і її вирішення. У вас є клас, що містить API методи, які повертають інформацію про користувачів:
39
40 * getUserInfo(int $id) - повертає обʼєкт user що містить ключі id, email, name
41 * getUserInfoAsArray(int $id) - повертає інформацію про користувача у вигляді масиву
42 * getUsersList() - повертає колекцію обʼєктів user
43
44 {{code language="php" layout="LINENUMBERS"}}
45 <?php
46 namespace App\Api\Procedures;
47
48 use App\Services\UserService;
49 use Ufo\JsonRpcBundle\ApiMethod\Interfaces\IRpcService;
50
51 class ExampleApi implements IRpcService
52 {
53 public function __construct(
54 protected UserService $userService
55 ) {}
56
57 public function getUserInfo(int $id): User
58 {
59 return $this->userService->getById($id);
60 }
61
62 public function getUserInfoAsArray(int $id): array
63 {
64 $user = $this->getUserInfo($id);
65 return [
66 'id' => $user->getId(),
67 'email' => $user->getEmail(),
68 'name' => $user->getName(),
69 ];
70 }
71
72 public function getUsersList(): array
73 {
74 return $this->userService->getAll();
75 }
76 }
77
78 {{/code}}
79
80 Поглянемо на документацію, що згенерована по цій інструкції
81
82 {{code language="json" layout="LINENUMBERS"}}
83 {
84 "envelope": "JSON-RPC-2.0/UFO-RPC-6",
85 "transport": "POST",
86 "target": "/api",
87 "contentType": "application/json",
88 "description": "",
89 "methods": {
90 "ExampleApi.getUserInfo": {
91 "name": "ExampleApi.getUserInfo",
92 "description": "",
93 "parameters": {
94 "id": {
95 "type": "integer",
96 "name": "id",
97 "description": "",
98 "optional": false
99 }
100 },
101 "returns": "object",
102 "responseFormat": "object"
103 },
104 "ExampleApi.getUserInfoAsArray": {
105 "name": "ExampleApi.getUserInfoAsArray",
106 "description": "",
107 "parameters": {
108 "id": {
109 "type": "integer",
110 "name": "id",
111 "description": "",
112 "optional": false
113 }
114 },
115 "returns": "array",
116 "responseFormat": "array"
117 },
118 "ExampleApi.getUsersList": {
119 "name": "ExampleApi.getUsersList",
120 "description": "",
121 "parameters": [],
122 "returns": "array",
123 "responseFormat": "array"
124 }
125 }
126 }
127 {{/code}}
128
129 Нас цікавлять returns та responseFormat (рядки 19-20 33-34 та 40-41), бачимо, що RPC Server інтерпретував User як object і ця інформація ніяким чином не говорить клієнту про те, як працювати з обʼєктом відповіді, які властивості в нього мають бути, так само як і немає інформації щодо відповідей двох інших методів, в обох випадках має повернутися масив і ми нічого не знаємо про його вміст.
130
131
132 Саме час додати атрибут Response. Тут є варіативність у використанні.
133
134 == **Варіант 1 - (одноразовий) підходить для методів, що повертають унікальний набір параметрів.** ==
135
136 Передача параметру $responseFormat, в якому у вигляді масиву ключ=>значення потрібно перерахувати всі параметри, що буду мати обʼєкт відповіді.
137
138 Ключ це назва параметру, а значення це тип даних.
139
140 (% class="row" %)
141 (((
142 (% class="col-xs-12 col-sm-6" %)
143 (((
144 {{code language="php" layout="LINENUMBERS"}}
145 <?php
146 namespace App\Api\Procedures;
147
148 use App\Services\UserService;
149 use Ufo\JsonRpcBundle\ApiMethod\Interfaces\IRpcService;
150 use Ufo\RpcObject\RPC;
151
152 class ExampleApi implements IRpcService
153 {
154 public function __construct(
155 protected UserService $userService
156 ) {}
157
158 #[RPC\Response(['id' => 'int', 'email' => 'string', 'name' => 'string'])]
159 public function getUserInfo(int $id): User
160 {
161 return $this->userService->getById($id);
162 }
163
164 #[RPC\Response(['id' => 'int', 'email' => 'string', 'name' => 'string'])]
165 public function getUserInfoAsArray(int $id): array
166 {
167 $user = $this->getUserInfo($id);
168 return [
169 'id' => $user->getId(),
170 'email' => $user->getEmail(),
171 'name' => $user->getName(),
172 ];
173 }
174
175 #[RPC\Response([['id' => 'int', 'email' => 'string', 'name' => 'string'])]]
176 public function getUsersList(): array
177 {
178 return $this->userService->getAll();
179 }
180 }
181
182 {{/code}}
183
184 Зверніть увагу на змінений формат документації праворуч, responseFormat тепер має деталізацію і надає чітке уявлення про структуру відповіді.
185 )))
186
187 (% class="col-xs-12 col-sm-6" %)
188 (((
189 {{code language="json" layout="LINENUMBERS" title="(% class=~"box warningmessage~" %)
190 (((
191 Для спрощення документації, в прикладі відповіді я приберу інші елементи окрім тих, що маю на меті продемонструвати
192 )))"}}
193 {
194 "methods": {
195 "ExampleApi.getUserInfo": {
196 "name": "ExampleApi.getUserInfo",
197 ...
198 "returns": "object",
199 "responseFormat": {
200 "id": "int",
201 "email": "string",
202 "name": "string"
203 }
204 },
205 "ExampleApi.getUserInfoAsArray": {
206 "name": "ExampleApi.getUserInfoAsArray",
207 ...
208 "returns": "array",
209 "responseFormat": {
210 "id": "int",
211 "email": "string",
212 "name": "string"
213 }
214 },
215 "ExampleApi.getUsersList": {
216 "name": "ExampleApi.getUsersList",
217 ...
218 "returns": "array",
219 "responseFormat": [
220 {
221 "id": "int",
222 "email": "string",
223 "name": "string"
224 }
225 ]
226 }
227 }
228 }
229 {{/code}}
230
231
232 )))
233 )))
234
235 Думаю, що ви звернули увагу на те, що використання масиву в такому прикладі здається не зручним, бо навіть в рамках лише одного цього класу ми використали його тричі і якщо потрібно буде змінити структуру властивостей користувача цей масив доведеться змінювати в багатьох місцях. Іншими словами це порушує [[DRY>>https://ru.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself]]. Тому я й вказав, що цей варіант підходить лише як одноразовий. В інших випадках вам потрібно використовувати інший підхід.
236
237 == **Варіант 2 - (рекомендований)** ==
238
239 Цей варіант передбачає, що вам потрібно створити класи що будуть виступати в ролі DTO (Data Transfer Object), це має бути клас, що має ті самі властивості, що і обʼєкт, який повертає ваш API метод.
240
241
242 (% class="row" %)
243 (((
244 (% class="col-xs-12 col-sm-6" %)
245 (((
246 {{code language="php" layout="LINENUMBERS"}}
247 <?php
248 namespace App\Api\DTO;
249
250 readonly class UserDTO
251 {
252 public int $id;
253 public string $email;
254 public string $name;
255 }
256 {{/code}}
257
258 {{code language="php" layout="LINENUMBERS"}}
259 <?php
260 namespace App\Api\Procedures;
261
262 use App\Services\UserService;
263 use Ufo\JsonRpcBundle\ApiMethod\Interfaces\IRpcService;
264 use Ufo\RpcObject\RPC;
265 use App\Api\DTO\UserDTO;
266
267 class ExampleApi implements IRpcService
268 {
269 public function __construct(
270 protected UserService $userService
271 ) {}
272
273 #[RPC\Response(dto: UserDTO::class)]
274 public function getUserInfo(int $id): User
275 {
276 return $this->userService->getById($id);
277 }
278
279 #[RPC\Response(dto: UserDTO::class)]
280 public function getUserInfoAsArray(int $id): array
281 {
282 $user = $this->getUserInfo($id);
283 return [
284 'id' => $user->getId(),
285 'email' => $user->getEmail(),
286 'name' => $user->getName(),
287 ];
288 }
289
290 #[RPC\Response(dto: UserDTO::class, collection: true)]
291 public function getUsersList(): array
292 {
293 return $this->userService->getAll();
294 }
295 }
296
297 {{/code}}
298 )))
299
300 (% class="col-xs-12 col-sm-6" %)
301 (((
302 {{code language="json" layout="LINENUMBERS" title="(% class=~"box warningmessage~" %)
303 (((
304 Для спрощення документації, в прикладі відповіді я приберу інші елементи окрім тих, що маю на меті продемонструвати
305 )))"}}
306 {
307 "methods": {
308 "ExampleApi.getUserInfo": {
309 "name": "ExampleApi.getUserInfo",
310 ...
311 "returns": "object",
312 "responseFormat": {
313 "id": "int",
314 "email": "string",
315 "name": "string"
316 }
317 },
318 "ExampleApi.getUserInfoAsArray": {
319 "name": "ExampleApi.getUserInfoAsArray",
320
321 "returns": "array",
322 "responseFormat": {
323 "id": "int",
324 "email": "string",
325 "name": "string"
326 }
327 },
328 "ExampleApi.getUsersList": {
329 "name": "ExampleApi.getUsersList",
330
331 "returns": "array",
332 "responseFormat": [
333 {
334 "id": "int",
335 "email": "string",
336 "name": "string"
337 }
338 ]
339 }
340 }
341 }
342 {{/code}}
343
344 (% class="box successmessage" %)
345 (((
346 Формат відповіді не змінився, але підтримувати і розширяти ваші API методи стало набагато легше і комфортніше
347 )))
348 )))
349 )))
350
351
352