AbstractApi::httpGet()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 3
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace CloudPlayDev\ConfluenceClient\Api;
5
6
7
use CloudPlayDev\ConfluenceClient\ConfluenceClient;
8
use CloudPlayDev\ConfluenceClient\Entity\Hydratable;
9
use CloudPlayDev\ConfluenceClient\Exception\ConfluencePhpClientException;
10
use CloudPlayDev\ConfluenceClient\Exception\HttpClientException;
11
use CloudPlayDev\ConfluenceClient\Exception\HttpServerException;
12
use CloudPlayDev\ConfluenceClient\Exception\HydrationException;
13
use Http\Client\Exception;
14
use JsonException;
15
use Psr\Http\Message\ResponseInterface;
16
use Webmozart\Assert\Assert;
17
use function array_filter;
18
use function array_merge;
19
use function get_class;
20
use function in_array;
21
use function json_encode;
22
use function sprintf;
23
24
abstract class AbstractApi
25
{
26
    /**
27
     * default rest API prefix for confluence
28
     */
29
    private const URI_PREFIX = '/rest/api/';
30
31
    private ConfluenceClient $client;
32
33
    public function __construct(ConfluenceClient $client)
34
    {
35
        $this->client = $client;
36
    }
37
38
    /**
39
     * @param string $uri
40
     * @param mixed[] $params
41
     * @param array<string, string> $headers
42
     * @return ResponseInterface
43
     * @throws Exception
44
     */
45
    protected function httpGet(string $uri, array $params = [], array $headers = []): ResponseInterface
46
    {
47
        return $this->client->getHttpClient()->get(self::prepareUri($uri, $params), $headers);
48
    }
49
50
    /**
51
     * @param string $uri
52
     * @param mixed[] $params
53
     * @param array<string, string> $headers
54
     * @return ResponseInterface
55
     * @throws Exception
56
     * @throws JsonException
57
     */
58
    protected function httpPut(string $uri, array $params = [], array $headers = []): ResponseInterface
59
    {
60
        $body = self::prepareJsonBody($params);
61
62
        if ($body !== '') {
63
            $headers = self::addJsonContentType($headers);
64
        }
65
66
        return $this->client->getHttpClient()->put(self::prepareUri($uri), $headers, $body);
67
    }
68
69
    /**
70
     * @param string $uri
71
     * @param mixed[] $queryParams
72
     * @param mixed[] $bodyData
73
     * @param array<string, string> $headers
74
     * @return ResponseInterface
75
     * @throws Exception
76
     * @throws JsonException
77
     */
78
    protected function httpPost(string $uri, array $queryParams = [], array $bodyData = [], array $headers = []): ResponseInterface
79
    {
80
        $body = self::prepareJsonBody($bodyData);
81
82
        if ($body !== '') {
83
            $headers = self::addJsonContentType($headers);
84
        }
85
86
        return $this->client->getHttpClient()->post(self::prepareUri($uri, $queryParams), $headers, $body);
87
    }
88
89
    /**
90
     * @param string $uri
91
     * @param mixed[] $params
92
     * @param array<string, string> $headers
93
     * @return ResponseInterface
94
     * @throws Exception
95
     * @throws JsonException
96
     */
97
    protected function httpDelete(string $uri, array $params = [], array $headers = []): ResponseInterface
98
    {
99
        $body = self::prepareJsonBody($params);
100
101
        if ($body !== '') {
102
            $headers = self::addJsonContentType($headers);
103
        }
104
105
        return $this->client->getHttpClient()->delete(self::prepareUri($uri), $headers, $body);
106
    }
107
108
    /**
109
     * @param array<string, string> $headers
110
     * @return array<string, string>
111
     */
112
    private static function addJsonContentType(array $headers): array
113
    {
114
        return array_merge(['Content-Type' => 'application/json'], $headers);
115
    }
116
117
    /**
118
     * @param mixed[] $params
119
     * @return string
120
     * @throws JsonException
121
     */
122
    private static function prepareJsonBody(array $params): string
123
    {
124
        return json_encode($params, JSON_THROW_ON_ERROR);
125
    }
126
127
    /**
128
     * @param string $uri
129
     * @param mixed[] $query
130
     * @return string
131
     */
132
    private static function prepareUri(string $uri, array $query = []): string
133
    {
134
        $query = array_filter($query, static fn($value): bool => null !== $value);
135
136
        $httpQueryParameter = http_build_query($query);
137
        if ($httpQueryParameter !== '') {
138
            $uri .= '?';
139
        }
140
        return sprintf('%s%s%s', self::URI_PREFIX, $uri, $httpQueryParameter);
141
    }
142
143
    /**
144
     * @psalm-template ExpectedType of Hydratable
145
     * @psalm-param class-string<ExpectedType> $class
146
     * @psalm-return ExpectedType
147
     *
148
     * @psalm-suppress InvalidReturnType https://psalm.dev/r/46c8264450
149
     * @psalm-suppress InvalidReturnStatement https://psalm.dev/r/46c8264450
150
     *
151
     * @param ResponseInterface $response
152
     * @param class-string $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
153
     * @return Hydratable
154
     * @throws ConfluencePhpClientException
155
     * @throws HttpClientException
156
     * @throws HttpServerException
157
     * @throws HydrationException
158
     * @throws JsonException
159
     */
160
    protected function hydrateResponse(ResponseInterface $response, string $class): Hydratable
161
    {
162
        $this->handleErrors($response);
163
164
        $contentType = $response->getHeaderLine('Content-Type');
165
166
        if (!str_starts_with($contentType, 'application/json')) {
167
            throw new HydrationException('The ModelHydrator cannot hydrate response with Content-Type: ' . $contentType);
168
        }
169
170
        $data = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
171
        Assert::isArray($data);
172
173
        $entity = $class::load($data);
174
        if (!$entity instanceof $class) {
175
            throw new HydrationException('An unexpected class was created: ' . get_class($entity));
176
        }
177
        return $entity;
178
    }
179
180
    /**
181
     * Throw the correct exception for this error.
182
     * @throws HttpClientException
183
     * @throws HttpServerException
184
     * @throws ConfluencePhpClientException
185
     */
186
    protected function handleErrors(ResponseInterface $response): void
187
    {
188
        $statusCode = $response->getStatusCode();
189
190
        if (in_array($statusCode, [200, 201, 202, 204], true)) {
191
            return;
192
        }
193
194
        if (500 <= $statusCode) {
195
            throw HttpServerException::serverError($response);
196
        }
197
198
        throw match ($statusCode) {
199
            400 => HttpClientException::badRequest($response),
200
            401 => HttpClientException::unauthorized($response),
201
            402 => HttpClientException::requestFailed($response),
202
            403 => HttpClientException::forbidden($response),
203
            404 => HttpClientException::notFound($response),
204
            409 => HttpClientException::conflict($response),
205
            413 => HttpClientException::payloadTooLarge($response),
206
            429 => HttpClientException::tooManyRequests($response),
207
            default => new ConfluencePhpClientException($response->getBody()->getContents(), $response->getStatusCode()),
208
        };
209
    }
210
211
    /**
212
     * @param string|int|null ...$parameter
213
     * @return string example `content/34636069`
214
     */
215
    protected static function getRestfulUri(...$parameter): string
216
    {
217
        $parameterString = implode('/', array_filter($parameter));
218
219
        if (!empty($parameterString)) {
220
            return $parameterString;
221
        }
222
223
        return '';
224
    }
225
}
226