Passed
Pull Request — master (#18)
by Frank
07:22
created

HttplugAsyncClient   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 142
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 14
lcom 1
cbo 10
dl 0
loc 142
ccs 60
cts 60
cp 1
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A sendTransaction() 0 11 1
A sendError() 0 11 1
A sendRequest() 0 21 3
B waitForResponses() 0 24 4
A verifyResponse() 0 11 4
1
<?php
2
declare(strict_types=1);
3
4
namespace TechDeCo\ElasticApmAgent\Client;
5
6
use Http\Client\HttpAsyncClient;
7
use Http\Discovery\Exception as DiscoveryException;
8
use Http\Discovery\HttpAsyncClientDiscovery;
9
use Http\Discovery\MessageFactoryDiscovery;
10
use Http\Message\MessageFactory;
11
use Http\Promise\Promise;
12
use Psr\Http\Message\RequestInterface;
13
use Psr\Http\Message\ResponseInterface;
14
use Psr\Log\LoggerInterface;
15
use TechDeCo\ElasticApmAgent\Client;
16
use TechDeCo\ElasticApmAgent\ClientConfiguration;
17
use TechDeCo\ElasticApmAgent\Exception\ClientException;
18
use TechDeCo\ElasticApmAgent\Request\Error;
19
use TechDeCo\ElasticApmAgent\Request\Transaction;
20
use Throwable;
21
use function count;
22
use function json_encode;
23
use function register_shutdown_function;
24
25
final class HttplugAsyncClient implements Client
26
{
27
    /**
28
     * @var ClientConfiguration
29
     */
30
    private $config;
31
32
    /**
33
     * @var HttpAsyncClient
34
     */
35
    private $httpClient;
36
37
    /**
38
     * @var MessageFactory
39
     */
40
    private $httpMessageFactory;
41
42
    /**
43
     * @var Promise[]
44
     */
45
    private $promiseList = [];
46
47
    /**
48
     * @var LoggerInterface
49
     */
50
    private $logger;
51
52
    /**
53
     * @throws DiscoveryException
54
     */
55 10
    public function __construct(
56
        LoggerInterface $logger,
57
        ClientConfiguration $config,
58
        ?HttpAsyncClient $httpClient,
59
        ?MessageFactory $httpMessageFactory
60
    ) {
61 10
        $this->logger             = $logger;
62 10
        $this->config             = $config;
63 10
        $this->httpClient         = $httpClient ?? HttpAsyncClientDiscovery::find();
64 10
        $this->httpMessageFactory = $httpMessageFactory ?? MessageFactoryDiscovery::find();
65
66 10
        register_shutdown_function([$this, 'waitForResponses']);
67 10
    }
68
69
    /**
70
     * @throws ClientException
71
     */
72 9
    public function sendTransaction(Transaction $transaction): void
73
    {
74 9
        $request = $this->httpMessageFactory->createRequest(
75 9
            'POST',
76 9
            $this->config->getTransactionsEndpoint(),
77 9
            [],
78 9
            json_encode($transaction)
79
        );
80
81 9
        $this->sendRequest($request);
82 8
    }
83
84
    /**
85
     * @throws ClientException
86
     */
87 1
    public function sendError(Error $error): void
88
    {
89 1
        $request = $this->httpMessageFactory->createRequest(
90 1
            'POST',
91 1
            $this->config->getErrorsEndpoint(),
92 1
            [],
93 1
            json_encode($error)
94
        );
95
96 1
        $this->sendRequest($request);
97 1
    }
98
99
    /**
100
     * @throws ClientException
101
     */
102 10
    private function sendRequest(RequestInterface $request): void
103
    {
104
        try {
105 10
            $requestCount = count($this->promiseList) + 1;
106
107 10
            $request = $request->withHeader('Content-Type', 'application/json');
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $request. This often makes code more readable.
Loading history...
108 10
            if ($this->config->needsAuthentication()) {
109 10
                $this->logger->debug('Adding authentication token to request');
110 10
                $request = $request->withHeader('Authorization', 'Bearer ' . $this->config->getToken());
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $request. This often makes code more readable.
Loading history...
111
            }
112
113 10
            $this->logger->debug('Sending asynchronous request #' . $requestCount);
114 10
            $this->promiseList[] = $this->httpClient->sendAsyncRequest($request);
115 1
        } catch (Throwable $e) {
116 1
            $this->logger->error('Encountered error while sending asynchronous request #' . $requestCount, [
117 1
                'exception' => $e,
118 1
                'message' => $e->getMessage(),
119
            ]);
120 1
            throw new ClientException('Could not send request due to configuration error', 0, $e);
121
        }
122 9
    }
123
124
    /**
125
     * @throws ClientException
126
     */
127 4
    public function waitForResponses(): void
128
    {
129 4
        $exceptionList = [];
130 4
        foreach ($this->promiseList as $index => $promise) {
131
            try {
132 4
                $requestCount = $index + 1;
133 4
                $this->logger->debug('Waiting for response on request #' . $requestCount);
134 4
                $this->verifyResponse($promise->wait());
135 2
                $this->logger->debug('Successful response on request #' . $requestCount);
136 3
            } catch (Throwable $e) {
137 3
                $this->logger->error('Encountered error in response for request #' . $requestCount, [
138 3
                    'exception' => $e,
139 3
                    'message' => $e->getMessage(),
140
                ]);
141 4
                $exceptionList[] = $e;
142
            }
143
        }
144
145 4
        $this->promiseList = [];
146
147 4
        if (! empty($exceptionList)) {
148 3
            throw ClientException::fromException('Encountered errors while resolving requests', ...$exceptionList);
149
        }
150 1
    }
151
152
    /**
153
     * @throws ClientException
154
     */
155 4
    private function verifyResponse(ResponseInterface $response): void
156
    {
157 4
        $status = $response->getStatusCode();
158
159 4
        if ($status >= 400 && $status < 500) {
160 1
            throw ClientException::fromResponse('Bad request', $response);
161
        }
162 3
        if ($status >= 500) {
163 2
            throw ClientException::fromResponse('APM internal server error', $response);
164
        }
165 2
    }
166
}
167