hengebytes/webservice-core-async-bundle

Symfony bundle for using http client as async

v1.0.0 2025-02-05 11:59 UTC

This package is auto-updated.

Last update: 2025-02-05 21:55:14 UTC


README

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

Installation

composer require hengebytes/webservice-core-async-bundle

Configuration

# config/packages/hb_webservice_core_async.yaml
hb_webservice_core_async:
    # Available options: 'symfony_params', 'settings_bundle', 'foo.bar.service_name'
    # If 'symfony_params' is used, the bundle will look for the parameters in the symfony param bag
    # If 'settings_bundle' is used, the bundle will look for the parameters in the hengebytes/settings-bundle
    # 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
    params_provider: ~ # default is null
    # by default the bundle will not use any cache
    cache:
        # second level cache adapter for persistent data default is null
        persistent_adapter: "app.cache.persistent"
        # first level cache adapter for runtime data default is null
        runtime_adapter: "app.cache.runtime"
    logs:
        # default is false if no parent is set
        enabled: true
        # configures the channel for the logs from monolog.yaml
        channel: webservice 
# config/packages/monolog.yaml
monolog:
    channels:
        - webservice
    handlers:
        app:
            level: info
            type: stream
            path: '%kernel.logs_dir%/webservice.log'
            channels: [ webservice ]

Add the bundle to your Kernel

// config/bundles.php
return [
    // ...
    Hengebytes\WebserviceCoreAsyncBundle\HBWebserviceCoreAsyncBundle::class => ['all' => true],
];

Usage

Create a service

// src/Service/MyService.php
namespace App\Service;

use Hengebytes\WebserviceCoreAsyncBundle\Handler\AsyncRequestHandler;
use Hengebytes\WebserviceCoreAsyncBundle\Response\AsyncResponse;
use Hengebytes\WebserviceCoreAsyncBundle\Request\WSRequest;

class MyService
{
    public function __construct(private readonly AsyncRequestHandler) {
    }

    // sync example
    public function execute(array $data): array
    {
        $request = new WSRequest(
                'my_service',
                '/oauth/tokens',
                RequestMethodEnum::POST,
                'sub_service',
                fn(array $response) => $response['expires_in'] ?? 0
        );
        $request->setAuthBasic('username', 'password');
        $request->setHeaders([
            'Content-Type' => 'application/json',
        ]);
        $request->setBody(json_encode($data));

        $result = $this->rh->request($request);
        // $result is a promise that will be resolved toArray() when the request is completed
        // you can return promise and resolve it in the controller when needed
        $data = $result->toArray();
        
        return $data;
    }
    
    // async example
    public function executeAsync(array $data): AsyncResponse
    {
        $request = new WSRequest(
                'my_service',
                '/profile',
                RequestMethodEnum::POST,
                'sub_service',
        );
        $request->setAuthBasic('username', 'password');
        $request->setHeaders([
            'Content-Type' => 'application/json',
        ]);
        $request->setBody(json_encode($data));

        return $this->rh->request($request);
    }
}

Create a controller

// src/Controller/MyController.php
namespace App\Controller;

use App\Service\MyService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

class MyController extends AbstractController
{
    public function __construct(private MyService $myService) {
    }

    public function index(Request $request): JsonResponse
    {
        $data = json_decode($request->getContent(), true);
        $result = $this->myService->execute($data);
        
        return $this->json(['result' => $result]);
    }
    
    public function async(Request $request): JsonResponse
    {
        $requestParams = $request->request->all();
        $requestParams['page'] = 1;
        $result1 = $this->myService->executeAsync($requestParams);
        
        $requestParams['page'] = 2;
        $result2 = $this->myService->executeAsync($requestParams);
        // do something else while the request is being processed
        
        $response1 = $result->toArray();
        $response2 = $result->toArray();
        
        return $this->json(['result' => array_merge($response1, $response2)]);
    }
}

Possible variants of configuration params in Settings when using settings_bundle

Possible variants of configuration params in symfony configuration when using symfony_params

parameters:
    hb_webservice_core_async.base_url.my_service: 'http://example.com'
    hb_webservice_core_async.base_url.my_service.my_subService: 'http://example2.com'
    hb_webservice_core_async.cache_ttl.my_service.customAction: 600
    hb_webservice_core_async.cache_ttl.my_service.action: 600
    hb_webservice_core_async.cache_ttl.my_service.my_subService.customAction: 300
    hb_webservice_core_async.timeout.my_service.customAction: 15
    hb_webservice_core_async.timeout.my_service.my_subService.customAction: 25
    hb_webservice_core_async.logs.store: 1
    hb_webservice_core_async.logs.store.customAction: 0
    hb_webservice_core_async.logs.mask_sensitive_data: 1
    hb_webservice_core_async.logs.mask_sensitive_member_pii: 1
    hb_webservice_core_async.logs.max_length: 900000

Validate Response

To be used in parsing response and validate it to throw exception if needed

// src/Middleware/MyResponseValidatorResponseModifier.php
namespace App\Middleware;

use Hengebytes\WebserviceCoreAsyncBundle\Middleware\ResponseModificationInterface;
use Hengebytes\WebserviceCoreAsyncBundle\Request\WSRequest;
use Hengebytes\WebserviceCoreAsyncBundle\Response\ParsedResponse;
// MyServiceResponseFailException should extend Hengebytes\WebserviceCoreAsyncBundle\Exception\ResponseFailException
use App\Exceptions\MyServiceResponseFailException;

class MyResponseValidatorResponseModifier implements ResponseModificationInterface
{
    public function modify(WSRequest $request, AsyncResponse $response): AsyncResponse
    {
        $response->addOnResponseReceivedCallback(new OnResponseReceivedCallback(
            function (ParsedResponse $parsedResponse) {
                if (isset($parsedResponse->response['errorKey'])) {
                    // this exception will be thrown when the response is received
                    $parsedResponse->exception = new MyServiceResponseFailException($parsedResponse->response['errorKey']);
                }
            }
        ));
    }

    public function supports(WSRequest $webService): bool
    {
        return $response->WSRequest->webService === 'my_service' 
        && $response->WSRequest->subService === 'my_subService';
    }
    
    public function getPriority(): int
    {
        return 0;
    }
}

Current Request Modifier Priorities

Higher priority will be executed first

Current Response Modifier Priorities

Higher priority will be executed first