Вікі-код для #[RPC\Response]
Показати останніх авторів
author | version | line-number | content |
---|---|---|---|
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 | ))) |