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

Остання зміна 2024/05/19 21:13 автором Ashterix

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