|
2 | 2 |
|
3 | 3 | This bundle provides a simple way to create asynchronous web services in Symfony. |
4 | 4 |
|
5 | | -## Installation |
6 | | - |
7 | | -```bash |
8 | | -composer require hengebytes/webservice-core-async-bundle |
9 | | -``` |
10 | | - |
11 | | -## Configuration |
12 | | - |
13 | | -```yaml |
14 | | -# config/packages/hb_webservice_core_async.yaml |
15 | | -hb_webservice_core_async: |
16 | | - # Available options: 'symfony_params', 'settings_bundle', 'foo.bar.service_name' |
17 | | - # If 'symfony_params' is used, the bundle will look for the parameters in the symfony param bag |
18 | | - # If 'settings_bundle' is used, the bundle will look for the parameters in the hengebytes/settings-bundle |
19 | | - # If 'foo.bar.service_name' is used, the bundle will look for the parameters in the service 'foo.bar.service_name' should implement Hengebytes\WebserviceCoreAsyncBundle\Provider\ParamsProviderInterface |
20 | | - params_provider: ~ # default is null |
21 | | - # by default the bundle will not use any cache |
22 | | - cache: |
23 | | - # second level cache adapter for persistent data default is null |
24 | | - persistent_adapter: "app.cache.persistent" |
25 | | - # first level cache adapter for runtime data default is null |
26 | | - runtime_adapter: "app.cache.runtime" |
27 | | - logs: |
28 | | - # default is false if no parent is set |
29 | | - enabled: true |
30 | | - # configures the channel for the logs from monolog.yaml |
31 | | - channel: webservice |
32 | | -``` |
33 | | -
|
34 | | -```yaml |
35 | | -# config/packages/monolog.yaml |
36 | | -monolog: |
37 | | - channels: |
38 | | - - webservice |
39 | | - handlers: |
40 | | - app: |
41 | | - level: info |
42 | | - type: stream |
43 | | - path: '%kernel.logs_dir%/webservice.log' |
44 | | - channels: [ webservice ] |
45 | | -``` |
46 | | -
|
47 | | -## Add the bundle to your Kernel |
48 | | -
|
49 | | -```php |
50 | | -// config/bundles.php |
51 | | -return [ |
52 | | - // ... |
53 | | - Hengebytes\WebserviceCoreAsyncBundle\HBWebserviceCoreAsyncBundle::class => ['all' => true], |
54 | | -]; |
55 | | -``` |
56 | | - |
57 | | -## Usage |
58 | | - |
59 | | -### Create a service |
60 | | - |
61 | | -```php |
62 | | -// src/Service/MyService.php |
63 | | -namespace App\Service; |
64 | | - |
65 | | -use Hengebytes\WebserviceCoreAsyncBundle\Handler\AsyncRequestHandler; |
66 | | -use Hengebytes\WebserviceCoreAsyncBundle\Response\AsyncResponse; |
67 | | -use Hengebytes\WebserviceCoreAsyncBundle\Request\WSRequest;use Hengebytes\WebserviceCoreAsyncBundle\Response\ModelPromise; |
68 | | - |
69 | | -class MyService |
70 | | -{ |
71 | | - public function __construct(private readonly AsyncRequestHandler $rh) { |
72 | | - } |
73 | | - |
74 | | - // sync example |
75 | | - public function execute(array $data): array |
76 | | - { |
77 | | - $request = new WSRequest( |
78 | | - 'my_service', |
79 | | - '/oauth/tokens', |
80 | | - RequestMethodEnum::POST, |
81 | | - 'sub_service', |
82 | | - fn(array $response) => $response['expires_in'] ?? 0 |
83 | | - ); |
84 | | - $request->setAuthBasic('username', 'password'); |
85 | | - $request->setHeaders([ |
86 | | - 'Content-Type' => 'application/json', |
87 | | - ]); |
88 | | - $request->setBody(json_encode($data)); |
89 | | - |
90 | | - $result = $this->rh->request($request); |
91 | | - // $result is a promise that will be resolved toArray() when the request is completed |
92 | | - // you can return promise and resolve it in the controller when needed |
93 | | - $data = $result->toArray(); |
94 | | - |
95 | | - return $data; |
96 | | - } |
97 | | - |
98 | | - // async example |
99 | | - public function executeAsync(array $data): AsyncResponse |
100 | | - { |
101 | | - $request = new WSRequest( |
102 | | - 'my_service', |
103 | | - '/profile', |
104 | | - RequestMethodEnum::POST, |
105 | | - 'sub_service', |
106 | | - ); |
107 | | - $request->setAuthBasic('username', 'password'); |
108 | | - $request->setHeaders([ |
109 | | - 'Content-Type' => 'application/json', |
110 | | - ]); |
111 | | - $request->setBody(json_encode($data)); |
112 | | - |
113 | | - return $this->rh->request($request); |
114 | | - } |
115 | | - |
116 | | - |
117 | | - /** |
118 | | - * async example with model |
119 | | - * @param array $data |
120 | | - * @return ModelPromise<SomeModel> |
121 | | - * @throws \Hengebytes\WebserviceCoreAsyncBundle\Exception\ConnectionInitException |
122 | | - */ |
123 | | - public function executeAsyncModel(array $data): ModelPromise |
124 | | - { |
125 | | - $request = new WSRequest( |
126 | | - 'my_service', |
127 | | - '/profile', |
128 | | - RequestMethodEnum::POST, |
129 | | - 'sub_service', |
130 | | - ); |
131 | | - $request->setAuthBasic('username', 'password'); |
132 | | - $request->setHeaders([ |
133 | | - 'Content-Type' => 'application/json', |
134 | | - ]); |
135 | | - $request->setBody(json_encode($data)); |
136 | | - |
137 | | - return $this->rh->requestModel($request, SomeModel::class); |
138 | | - } |
139 | | -} |
140 | | -``` |
141 | | - |
142 | | -### Create a controller |
143 | | - |
144 | | -```php |
145 | | -// src/Controller/MyController.php |
146 | | -namespace App\Controller; |
147 | | - |
148 | | -use App\Service\MyService; |
149 | | -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; |
150 | | -use Symfony\Component\HttpFoundation\JsonResponse; |
151 | | -use Symfony\Component\HttpFoundation\Request; |
152 | | - |
153 | | -class MyController extends AbstractController |
154 | | -{ |
155 | | - public function __construct(private MyService $myService) { |
156 | | - } |
157 | | - |
158 | | - public function index(Request $request): JsonResponse |
159 | | - { |
160 | | - $data = json_decode($request->getContent(), true); |
161 | | - $result = $this->myService->execute($data); |
162 | | - |
163 | | - return $this->json(['result' => $result]); |
164 | | - } |
165 | | - |
166 | | - public function async(Request $request): JsonResponse |
167 | | - { |
168 | | - $requestParams = $request->request->all(); |
169 | | - $requestParams['page'] = 1; |
170 | | - $result1 = $this->myService->executeAsync($requestParams); |
171 | | - |
172 | | - $requestParams['page'] = 2; |
173 | | - $result2 = $this->myService->executeAsync($requestParams); |
174 | | - // do something else while the request is being processed |
175 | | - |
176 | | - $response1 = $result->toArray(); |
177 | | - $response2 = $result->toArray(); |
178 | | - |
179 | | - return $this->json(['result' => array_merge($response1, $response2)]); |
180 | | - } |
181 | | - |
182 | | - |
183 | | - public function asyncWithModels(Request $request): JsonResponse |
184 | | - { |
185 | | - $requestParams = $request->request->all(); |
186 | | - $requestParams['page'] = 1; |
187 | | - $result1 = $this->myService->executeAsyncModel($requestParams); |
188 | | - |
189 | | - $requestParams['page'] = 2; |
190 | | - $result2 = $this->myService->executeAsyncModel($requestParams); |
191 | | - // do something else while the request is being processed |
192 | | - |
193 | | - $model1 = $result->getModel(); |
194 | | - $model2 = $result->getModel(); |
195 | | - |
196 | | - return $this->json([ |
197 | | - 'page1' => $model1, |
198 | | - 'page2' $model2 |
199 | | - ]); |
200 | | - } |
201 | | -} |
202 | | -``` |
203 | | - |
204 | | -### Possible variants of configuration params in Settings when using `settings_bundle` |
205 | | - |
206 | | -| Key | Value | |
207 | | -|-----------------------------------------------------------|-----------------------| |
208 | | -| `my_service/base_url` | `http://example.com` | |
209 | | -| OVERRIDE`my_service/my_subService/base_url` | `http://example2.com` | |
210 | | -| `cache/my_service/customAction/ttl` | 600 | |
211 | | -| IF NO CUSTOM ACTION`cache/my_service/action/ttl` | 600 | |
212 | | -| OVERRIDE`cache/my_service/my_subService/customAction/ttl` | 300 | |
213 | | -| `timeout/my_service/customAction` | 15 | |
214 | | -| OVERRIDE`timeout/my_service/my_subService/customAction` | 25 | |
215 | | -| `logs/store` | 1 | |
216 | | -| OVERRIDE`logs/store/customAction` | 0 | |
217 | | -| `logs/mask_sensitive_data` | 1 | |
218 | | -| `logs/mask_sensitive_member_pii` | 1 | |
219 | | -| `logs/max_length` | 900000 | |
220 | | - |
221 | | -### Possible variants of configuration params in symfony configuration when using `symfony_params` |
222 | | - |
223 | | -```yaml |
224 | | -parameters: |
225 | | - hb_webservice_core_async.base_url.my_service: 'http://example.com' |
226 | | - hb_webservice_core_async.base_url.my_service.my_subService: 'http://example2.com' |
227 | | - hb_webservice_core_async.cache_ttl.my_service.customAction: 600 |
228 | | - hb_webservice_core_async.cache_ttl.my_service.action: 600 |
229 | | - hb_webservice_core_async.cache_ttl.my_service.my_subService.customAction: 300 |
230 | | - hb_webservice_core_async.timeout.my_service.customAction: 15 |
231 | | - hb_webservice_core_async.timeout.my_service.my_subService.customAction: 25 |
232 | | - hb_webservice_core_async.logs.store: 1 |
233 | | - hb_webservice_core_async.logs.store.customAction: 0 |
234 | | - hb_webservice_core_async.logs.mask_sensitive_data: 1 |
235 | | - hb_webservice_core_async.logs.mask_sensitive_member_pii: 1 |
236 | | - hb_webservice_core_async.logs.max_length: 900000 |
237 | | -``` |
238 | | -
|
239 | | -### Validate Response |
240 | | -
|
241 | | -To be used in parsing response and validate it to throw exception if needed |
242 | | -
|
243 | | -```php |
244 | | -// src/Middleware/MyResponseValidatorResponseModifier.php |
245 | | -namespace App\Middleware; |
246 | | - |
247 | | -use Hengebytes\WebserviceCoreAsyncBundle\Middleware\ResponseModificationInterface; |
248 | | -use Hengebytes\WebserviceCoreAsyncBundle\Request\WSRequest; |
249 | | -use Hengebytes\WebserviceCoreAsyncBundle\Response\ParsedResponse; |
250 | | -// MyServiceResponseFailException should extend Hengebytes\WebserviceCoreAsyncBundle\Exception\ResponseFailException |
251 | | -use App\Exceptions\MyServiceResponseFailException; |
252 | | - |
253 | | -class MyResponseValidatorResponseModifier implements ResponseModificationInterface |
254 | | -{ |
255 | | - public function modify(WSRequest $request, AsyncResponse $response): AsyncResponse |
256 | | - { |
257 | | - $response->addOnResponseReceivedCallback(new OnResponseReceivedCallback( |
258 | | - function (ParsedResponse $parsedResponse) { |
259 | | - if (isset($parsedResponse->response['errorKey'])) { |
260 | | - // this exception will be thrown when the response is received |
261 | | - $parsedResponse->exception = new MyServiceResponseFailException($parsedResponse->response['errorKey']); |
262 | | - } |
263 | | - } |
264 | | - )); |
265 | | - } |
266 | | - |
267 | | - public function supports(WSRequest $webService): bool |
268 | | - { |
269 | | - return $response->WSRequest->webService === 'my_service' |
270 | | - && $response->WSRequest->subService === 'my_subService'; |
271 | | - } |
272 | | - |
273 | | - public function getPriority(): int |
274 | | - { |
275 | | - return 0; |
276 | | - } |
277 | | -} |
278 | | -``` |
279 | | - |
280 | | -### Current Request Modifier Priorities |
281 | | - |
282 | | -Higher priority will be executed first |
283 | | - |
284 | | -| Key | Value | |
285 | | -|---------------------------|-------| |
286 | | -| `BaseUrlRequestModifier` | 0 | |
287 | | -| `CacheTTLRequestModifier` | 0 | |
288 | | - |
289 | | -### Current Response Modifier Priorities |
290 | | - |
291 | | -Higher priority will be executed first |
292 | | - |
293 | | -| Key | Value | Condition | Could be disabled | |
294 | | -|----------------------------------------|-------|-----------------------------------------------------------------------|-------------------| |
295 | | -| `LockResponseLoaderResponseModifier` | 250 | `$response->isCached` | With Cache | |
296 | | -| `ReloadLockedResponseResponseModifier` | 240 | `$response->isCached` | With Cache | |
297 | | -| `ResponseParserResponseModifier` | 220 | Always | - | |
298 | | -| `LogResponseModifier` | 210 | `!$response->isCached` | With Logs | |
299 | | -| `StoreToCacheResponseModifier` | -200 | `!$response->isCached` | With Cache | |
300 | | -| `RequestUnlockResponseModifier` | -210 | `!$response->isCached && $response->WSRequest->isCachable()` | With Cache | |
301 | | -| `InvalidateCacheResponseModifier` | -220 | `!$response->isCached && !$response->WSRequest->isGETRequestMethod()` | With Cache | |
302 | | - |
303 | | -### You should create model provider for the model promise |
304 | | - |
305 | | -it will be automatically registered based on interface implementation |
306 | | -and will be automatically called when the promise is resolved |
307 | | - |
308 | | -```php |
309 | | -// src/Provider/MyModelProvider.php |
310 | | -namespace App\Provider; |
311 | | - |
312 | | -use Hengebytes\WebserviceCoreAsyncBundle\Provider\ModelProviderInterface; |
313 | | -use App\Model\SomeModel; |
314 | | -use App\Model\SomeOtherModel; |
315 | | - |
316 | | -class MyModelProvider implements ModelProviderInterface |
317 | | -{ |
318 | | - public function getModel(mixed $data, ModelProvider $modelProvider): object |
319 | | - { |
320 | | - $data = $data['data'] ?? []; |
321 | | - $someOtherModel = $modelProvider->getModel(SomeOtherModel::class, $data['someOtherModel'] ?? []); |
322 | | - $someModel = new SomeModel($data) |
323 | | - $someModel->setSomeOtherModel($someOtherModel); |
324 | | - |
325 | | - return $someModel; |
326 | | - } |
327 | | -} |
328 | | -``` |
| 5 | +## Documentation |
| 6 | + |
| 7 | +- [Installation](docs/index.md) |
| 8 | +- [Configuration](docs/configuration/index.md) |
| 9 | +- [Usage](docs/usage/index.md) |
| 10 | + - [Usage](docs/validation/index.md) |
| 11 | + - [Validation](docs/validation/index.md) |
| 12 | + - [Model](docs/model/index.md) |
| 13 | +- [Logs](docs/logs/index.md) |
| 14 | +- [Cache](docs/cache/index.md) |
| 15 | +- [Settings](docs/settings/index.md) |
| 16 | +- [Tests](docs/tests/index.md) |
| 17 | +- [Custom Request Handler](docs/advanced/customRH.md) |
0 commit comments