Passed
Push — master ( d81171...ddb4d4 )
by Frank
02:35
created

HttplugAsyncClient   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 120
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 14
lcom 1
cbo 9
dl 0
loc 120
ccs 47
cts 47
cp 1
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A sendTransaction() 0 11 1
A sendError() 0 11 1
A sendRequest() 0 13 3
A waitForResponses() 0 17 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 TechDeCo\ElasticApmAgent\Client;
15
use TechDeCo\ElasticApmAgent\ClientConfiguration;
16
use TechDeCo\ElasticApmAgent\Exception\ClientException;
17
use TechDeCo\ElasticApmAgent\Request\Error;
18
use TechDeCo\ElasticApmAgent\Request\Transaction;
19
use Throwable;
20
use function json_encode;
21
use function register_shutdown_function;
22
23
final class HttplugAsyncClient implements Client
24
{
25
    /**
26
     * @var ClientConfiguration
27
     */
28
    private $config;
29
30
    /**
31
     * @var HttpAsyncClient
32
     */
33
    private $httpClient;
34
35
    /**
36
     * @var MessageFactory
37
     */
38
    private $httpMessageFactory;
39
40
    /**
41
     * @var Promise[]
42
     */
43
    private $promiseList = [];
44
45
    /**
46
     * @throws DiscoveryException
47
     */
48 8
    public function __construct(
49
        ClientConfiguration $config,
50
        ?HttpAsyncClient $httpClient,
51
        ?MessageFactory $httpMessageFactory
52
    ) {
53 8
        $this->config             = $config;
54 8
        $this->httpClient         = $httpClient ?? HttpAsyncClientDiscovery::find();
55 8
        $this->httpMessageFactory = $httpMessageFactory ?? MessageFactoryDiscovery::find();
56
57 8
        register_shutdown_function([$this, 'waitForResponses']);
58 8
    }
59
60
    /**
61
     * @throws ClientException
62
     */
63 7
    public function sendTransaction(Transaction $transaction): void
64
    {
65 7
        $request = $this->httpMessageFactory->createRequest(
66 7
            'POST',
67 7
            $this->config->getTransactionsEndpoint(),
68 7
            [],
69 7
            json_encode($transaction)
70
        );
71
72 7
        $this->sendRequest($request);
73 6
    }
74
75
    /**
76
     * @throws ClientException
77
     */
78 1
    public function sendError(Error $error): void
79
    {
80 1
        $request = $this->httpMessageFactory->createRequest(
81 1
            'POST',
82 1
            $this->config->getErrorsEndpoint(),
83 1
            [],
84 1
            json_encode($error)
85
        );
86
87 1
        $this->sendRequest($request);
88 1
    }
89
90
    /**
91
     * @throws ClientException
92
     */
93 8
    private function sendRequest(RequestInterface $request): void
94
    {
95
        try {
96 8
            $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...
97 8
            if ($this->config->needsAuthentication()) {
98 8
                $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...
99
            }
100
101 8
            $this->promiseList[] = $this->httpClient->sendAsyncRequest($request);
102 1
        } catch (\Throwable $e) {
103 1
            throw new ClientException('Could not send request due to configuration error', 0, $e);
104
        }
105 7
    }
106
107
    /**
108
     * @throws ClientException
109
     */
110 4
    public function waitForResponses(): void
111
    {
112 4
        $exceptionList = [];
113 4
        foreach ($this->promiseList as $promise) {
114
            try {
115 4
                $this->verifyResponse($promise->wait());
116 3
            } catch (Throwable $e) {
117 4
                $exceptionList[] = $e;
118
            }
119
        }
120
121 4
        $this->promiseList = [];
122
123 4
        if (! empty($exceptionList)) {
124 3
            throw ClientException::fromException('Encountered errors while resolving requests', ...$exceptionList);
125
        }
126 1
    }
127
128
    /**
129
     * @throws ClientException
130
     */
131 4
    private function verifyResponse(ResponseInterface $response): void
132
    {
133 4
        $status = $response->getStatusCode();
134
135 4
        if ($status >= 400 && $status < 500) {
136 1
            throw ClientException::fromResponse('Bad request', $response);
137
        }
138 3
        if ($status >= 500) {
139 2
            throw ClientException::fromResponse('APM internal server error', $response);
140
        }
141 2
    }
142
}
143