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

Версія 5.1 додана 2024/05/16 11:42 автором 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 "contentType": "application/json",
86 "description": "",
87 "transport": {
88 "sync": {
89 "scheme": "https",
90 "host": "example.com",
91 "path": "/api",
92 "method": "POST"
93 }
94 },
95 "methods": {
96 "ExampleApi.getUserInfo": {
97 "name": "ExampleApi.getUserInfo",
98 "description": "",
99 "parameters": {
100 "id": {
101 "type": "integer",
102 "name": "id",
103 "description": "",
104 "optional": false
105 }
106 },
107 "returns": "object",
108 "responseFormat": "object"
109 },
110 "ExampleApi.getUserInfoAsArray": {
111 "name": "ExampleApi.getUserInfoAsArray",
112 "description": "",
113 "parameters": {
114 "id": {
115 "type": "integer",
116 "name": "id",
117 "description": "",
118 "optional": false
119 }
120 },
121 "returns": "array",
122 "responseFormat": "array"
123 },
124 "ExampleApi.getUsersList": {
125 "name": "ExampleApi.getUsersList",
126 "description": "",
127 "parameters": [],
128 "returns": "array",
129 "responseFormat": "array"
130 }
131 }
132 }
133 {{/code}}
134
135 Нас цікавлять returns та responseFormat (рядки 19-20 33-34 та 40-41), бачимо, що RPC Server інтерпретував User як object і ця інформація ніяким чином не говорить клієнту про те, як працювати з обʼєктом відповіді, які властивості в нього мають бути, так само як і немає інформації щодо відповідей двох інших методів, в обох випадках має повернутися масив і ми нічого не знаємо про його вміст.
136
137
138 Саме час додати атрибут Response. Тут є варіативність у використанні.
139
140 == **Варіант 1 - (одноразовий) підходить для методів, що повертають унікальний набір параметрів.** ==
141
142 Передача параметру $responseFormat, в якому у вигляді масиву ключ=>значення потрібно перерахувати всі параметри, що буде мати обʼєкт відповіді.
143
144 Ключ це назва параметру, а значення це тип даних.
145
146 (% class="row" %)
147 (((
148 (% class="col-xs-12 col-sm-6" %)
149 (((
150 {{code language="php" layout="LINENUMBERS"}}
151 <?php
152 namespace App\Api\Procedures;
153
154 use App\Services\UserService;
155 use Ufo\JsonRpcBundle\ApiMethod\Interfaces\IRpcService;
156 use Ufo\RpcObject\RPC;
157
158 class ExampleApi implements IRpcService
159 {
160 public function __construct(
161 protected UserService $userService
162 ) {}
163
164 #[RPC\Response(['id' => 'int', 'email' => 'string', 'name' => 'string'])]
165 public function getUserInfo(int $id): User
166 {
167 return $this->userService->getById($id);
168 }
169
170 #[RPC\Response(['id' => 'int', 'email' => 'string', 'name' => 'string'])]
171 public function getUserInfoAsArray(int $id): array
172 {
173 $user = $this->getUserInfo($id);
174 return [
175 'id' => $user->getId(),
176 'email' => $user->getEmail(),
177 'name' => $user->getName(),
178 ];
179 }
180
181 #[RPC\Response([['id' => 'int', 'email' => 'string', 'name' => 'string'])]]
182 public function getUsersList(): array
183 {
184 return $this->userService->getAll();
185 }
186 }
187
188 {{/code}}
189
190 Зверніть увагу на змінений формат документації праворуч, responseFormat тепер має деталізацію і надає чітке уявлення про структуру відповіді.
191 )))
192
193 (% class="col-xs-12 col-sm-6" %)
194 (((
195 {{code language="json" layout="LINENUMBERS" title="(% class=~"box warningmessage~" %)
196 (((
197 Для спрощення документації, в прикладі відповіді я приберу інші елементи окрім тих, що маю на меті продемонструвати
198 )))"}}
199 {
200 "methods": {
201 "ExampleApi.getUserInfo": {
202 "name": "ExampleApi.getUserInfo",
203 ...,
204 "returns": "object",
205 "responseFormat": {
206 "id": "int",
207 "email": "string",
208 "name": "string"
209 }
210 },
211 "ExampleApi.getUserInfoAsArray": {
212 "name": "ExampleApi.getUserInfoAsArray",
213 ...,
214 "returns": "array",
215 "responseFormat": {
216 "id": "int",
217 "email": "string",
218 "name": "string"
219 }
220 },
221 "ExampleApi.getUsersList": {
222 "name": "ExampleApi.getUsersList",
223 ...,
224 "returns": "array",
225 "responseFormat": [
226 {
227 "id": "int",
228 "email": "string",
229 "name": "string"
230 }
231 ]
232 }
233 }
234 }
235 {{/code}}
236
237
238 )))
239 )))
240
241 Думаю, що ви звернули увагу на те, що використання масиву в такому прикладі здається не зручним, бо навіть в рамках лише одного цього класу ми використали його тричі і якщо потрібно буде змінити структуру властивостей користувача цей масив доведеться змінювати в багатьох місцях. Іншими словами це порушує [[DRY>>https://ru.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself]]. Тому я й вказав, що цей варіант підходить лише як одноразовий. В інших випадках вам потрібно використовувати інший підхід.
242
243 == **Варіант 2 - (рекомендований)** ==
244
245 Цей варіант передбачає, що вам потрібно створити класи що будуть виступати в ролі DTO (Data Transfer Object), це має бути клас, що має ті самі властивості, що і обʼєкт, який повертає ваш API метод.
246
247
248 (% class="row" %)
249 (((
250 (% class="col-xs-12 col-sm-6" %)
251 (((
252 {{code language="php" layout="LINENUMBERS"}}
253 <?php
254 namespace App\Api\DTO;
255
256 readonly class UserDTO
257 {
258 public int $id;
259 public string $email;
260 public string $name;
261 }
262 {{/code}}
263
264 {{code language="php" layout="LINENUMBERS"}}
265 <?php
266 namespace App\Api\Procedures;
267
268 use App\Services\UserService;
269 use Ufo\JsonRpcBundle\ApiMethod\Interfaces\IRpcService;
270 use Ufo\RpcObject\RPC;
271 use App\Api\DTO\UserDTO;
272
273 class ExampleApi implements IRpcService
274 {
275 public function __construct(
276 protected UserService $userService
277 ) {}
278
279 #[RPC\Response(dto: UserDTO::class)]
280 public function getUserInfo(int $id): User
281 {
282 return $this->userService->getById($id);
283 }
284
285 #[RPC\Response(dto: UserDTO::class)]
286 public function getUserInfoAsArray(int $id): array
287 {
288 $user = $this->getUserInfo($id);
289 return [
290 'id' => $user->getId(),
291 'email' => $user->getEmail(),
292 'name' => $user->getName(),
293 ];
294 }
295
296 #[RPC\Response(dto: UserDTO::class, collection: true)]
297 public function getUsersList(): array
298 {
299 return $this->userService->getAll();
300 }
301 }
302
303 {{/code}}
304 )))
305
306 (% class="col-xs-12 col-sm-6" %)
307 (((
308 {{code language="json" layout="LINENUMBERS" title="(% class=~"box warningmessage~" %)
309 (((
310 Для спрощення документації, в прикладі відповіді я приберу інші елементи окрім тих, що маю на меті продемонструвати
311 )))"}}
312 {
313 "methods": {
314 "ExampleApi.getUserInfo": {
315 "name": "ExampleApi.getUserInfo",
316 ...,
317 "returns": "object",
318 "responseFormat": {
319 "id": "int",
320 "email": "string",
321 "name": "string"
322 }
323 },
324 "ExampleApi.getUserInfoAsArray": {
325 "name": "ExampleApi.getUserInfoAsArray",
326 ...,
327 "returns": "array",
328 "responseFormat": {
329 "id": "int",
330 "email": "string",
331 "name": "string"
332 }
333 },
334 "ExampleApi.getUsersList": {
335 "name": "ExampleApi.getUsersList",
336 ...,
337 "returns": "array",
338 "responseFormat": [
339 {
340 "id": "int",
341 "email": "string",
342 "name": "string"
343 }
344 ]
345 }
346 }
347 }
348 {{/code}}
349
350 (% class="box successmessage" %)
351 (((
352 Формат відповіді не змінився, але підтримувати і розширяти ваші API методи стало набагато легше і комфортніше
353 )))
354 )))
355 )))