Вікі-код для #[RPC\Response]
Остання зміна 2024/05/19 21:13 автором Ashterix
Показати останніх авторів
author | version | line-number | content |
---|---|---|---|
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 | ))) |