Skip to content

Commit 09e28dc

Browse files
author
Maelmin Henge
committed
update docs, introduce custom request handler
1 parent f474037 commit 09e28dc

File tree

17 files changed

+785
-382
lines changed

17 files changed

+785
-382
lines changed

README.md

Lines changed: 13 additions & 324 deletions
Original file line numberDiff line numberDiff line change
@@ -2,327 +2,16 @@
22

33
This bundle provides a simple way to create asynchronous web services in Symfony.
44

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)

docs/advanced/customRH.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
### Custom Request Handlers
2+
You can create your own request handler by extending the `Hengebytes\WebserviceCoreAsyncBundle\Request\BaseRequestHandler` interface. This allows you to customize the way the bundle made requests.
3+
___
4+
5+
```php
6+
// src/Handler/MyCustomRequestHandler.php
7+
namespace App\Handler;
8+
9+
use Hengebytes\WebserviceCoreAsyncBundle\Enum\RequestMethodEnum;
10+
use Hengebytes\WebserviceCoreAsyncBundle\Handler\BaseRequestHandler;
11+
use Hengebytes\WebserviceCoreAsyncBundle\Request\WSRequest;
12+
use Symfony\Component\HttpClient\Response\JsonMockResponse;
13+
use Symfony\Contracts\HttpClient\ResponseInterface;
14+
15+
class MyCustomRequestHandler extends BaseRequestHandler
16+
{
17+
/** @throws TransportExceptionInterface */
18+
protected function performRequest(WSRequest $request): ResponseInterface;
19+
{
20+
$options = $request->getOptions();
21+
$curl = curl_init();
22+
curl_setopt($curl, CURLOPT_HTTPHEADER, $options['headers']);
23+
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
24+
if ($request->requestMethod === RequestMethodEnum::POST) {
25+
curl_setopt($curl, CURLOPT_POST, true);
26+
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($options['json'], JSON_UNESCAPED_SLASHES));
27+
} else {
28+
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $request->requestMethod->name);
29+
}
30+
if (isset($options['query'])) {
31+
$action .= '?' . http_build_query($options['query']);
32+
}
33+
curl_setopt($curl, CURLOPT_URL, $options['base_uri'] . $action);
34+
35+
$response = curl_exec($curl);
36+
curl_close($curl);
37+
38+
return new JsonMockResponse($response);
39+
}
40+
}
41+
```

0 commit comments

Comments
 (0)