Вікі-код для #[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 | "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 |