Passed
Pull Request — master (#99)
by Dante
01:14
created

BaseClient::put()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 8
rs 10
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * BEdita, API-first content management framework
6
 * Copyright 2025 ChannelWeb Srl, Chialab Srl
7
 *
8
 * This file is part of BEdita: you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published
10
 * by the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
14
 */
15
16
namespace BEdita\WebTools\Http;
17
18
use Cake\Core\App;
19
use Cake\Core\Configure;
20
use Cake\Core\InstanceConfigTrait;
21
use Cake\Http\Client;
22
use Cake\Http\Client\Response;
23
use Cake\Log\LogTrait;
24
use Cake\Validation\Validator;
25
26
/**
27
 * Base class for clients.
28
 */
29
abstract class BaseClient
30
{
31
    use InstanceConfigTrait;
32
    use LogTrait;
33
34
    /**
35
     * Default configuration.
36
     *
37
     * @var array
38
     */
39
    protected $_defaultConfig = [
40
        'auth' => null,
41
        'logLevel' => 'error',
42
        'url' => null,
43
    ];
44
45
    /**
46
     * The HTTP client.
47
     *
48
     * @var \Cake\Http\Client
49
     */
50
    protected $client;
51
52
    /**
53
     * Constructor. Initialize HTTP client.
54
     *
55
     * @param array $config The configuration
56
     */
57
    public function __construct(array $config = [])
58
    {
59
        $config += (array)Configure::read($this->defaultConfigName(), []);
60
        $this->setConfig($config);
61
        $this->validateConf($this->getValidator());
62
        $this->createClient();
63
    }
64
65
    /**
66
     * Get default config name.
67
     * It's the name of the client class without `Client` suffix.
68
     *
69
     * @return string
70
     */
71
    protected function defaultConfigName(): string
72
    {
73
        $shortName = App::shortName(static::class, 'Http', 'Client');
74
        [$plugin, $name] = pluginSplit($shortName);
75
76
        return $name;
77
    }
78
79
    /**
80
     * Return the Validator object
81
     *
82
     * @return \Cake\Validation\Validator
83
     */
84
    protected function getValidator(): Validator
85
    {
86
        $validator = new Validator();
87
88
        return $validator
89
            ->requirePresence('url')
90
            ->notEmptyString('url');
91
    }
92
93
    /**
94
     * Validate configuration data.
95
     *
96
     * @param \Cake\Validation\Validator $validator The validator object
97
     * @return void
98
     */
99
    protected function validateConf(Validator $validator): void
100
    {
101
        $errors = $validator->validate($this->getConfig());
102
        if (!empty($errors)) {
103
            throw new \InvalidArgumentException(sprintf('%s client config not valid: %s', static::class, json_encode($errors)));
104
        }
105
    }
106
107
    /**
108
     * Create JSON HTTP client.
109
     *
110
     * @return void
111
     */
112
    protected function createClient(): void
113
    {
114
        $parsedUrl = parse_url($this->getConfig('url'));
115
        $options = [
116
            'host' => $parsedUrl['host'],
117
            'scheme' => $parsedUrl['scheme'],
118
            'path' => $parsedUrl['path'] ?? '',
119
            'headers' => [
120
                'Accept' => 'application/json',
121
                'Content-Type' => 'application/json',
122
            ],
123
        ] + $this->getConfig();
124
        $this->client = new Client($options);
125
    }
126
127
    /**
128
     * Get the correct relative url.
129
     *
130
     * @param string $url The relative url
131
     * @return string
132
     */
133
    protected function getUrl(string $url): string
134
    {
135
        if (strpos($url, 'https://') === 0) {
136
            return $url;
137
        }
138
        $base = trim($this->client->getConfig('path'), '/');
139
        $url = trim($url, '/');
140
141
        return sprintf('%s/%s', $base, $url);
142
    }
143
144
    /**
145
     * Log API call.
146
     *
147
     * @param string $call The API call
148
     * @param string $url The API url
149
     * @param string $payload The json payload
150
     * @param \Cake\Http\Client\Response $response The API response
151
     * @return ?string
152
     */
153
    protected function logCall(string $call, string $url, string $payload, Response $response): ?string
154
    {
155
        $level = $this->getConfig('logLevel') ?? 'error';
156
        if (!in_array($level, ['error', 'debug'])) {
157
            return null;
158
        }
159
        $result = $response->isOk() ? '' : 'error';
160
        if ($level === 'error' && empty($result)) {
161
            return null;
162
        }
163
        $level = $result === 'error' ? 'error' : $level;
164
        $message = sprintf(
165
            '%s API %s | %s %s | with status %s: %s - Payload: %s',
166
            $response->isOk() ? '[OK]' : '[ERROR]',
167
            $this->defaultConfigName(),
168
            $call,
169
            $url,
170
            $response->getStatusCode(),
171
            (string)$response->getBody(),
172
            $payload
173
        );
174
        $message = trim($message);
175
        $this->log($message, $level);
176
177
        return $message;
178
    }
179
180
    /**
181
     * Get http client
182
     *
183
     * @return \Cake\Http\Client The client
184
     */
185
    public function getHttpClient(): Client
186
    {
187
        return $this->client;
188
    }
189
190
    /**
191
     * Get request.
192
     *
193
     * @param string $url The request url
194
     * @param array $data The query data
195
     * @param array $options Request options
196
     * @return \Cake\Http\Client\Response
197
     */
198
    public function get(string $url, array $data = [], array $options = []): Response
199
    {
200
        $apiUrl = $this->getUrl($url);
201
        $response = $this->client->get($apiUrl, $data, $options);
202
        $this->logCall('/GET', $apiUrl, json_encode($data), $response);
203
204
        return $response;
205
    }
206
207
    /**
208
     * Post request.
209
     *
210
     * @param string $url The request url
211
     * @param array $data The post data
212
     * @param array $options Request options
213
     * @return \Cake\Http\Client\Response
214
     */
215
    public function post(string $url, array $data = [], array $options = []): Response
216
    {
217
        $data = json_encode($data);
218
        $apiUrl = $this->getUrl($url);
219
        $response = $this->client->post($apiUrl, $data, $options);
220
        $this->logCall('/POST', $apiUrl, $data, $response);
221
222
        return $response;
223
    }
224
225
    /**
226
     * Patch request.
227
     *
228
     * @param string $url The request url
229
     * @param array $data The post data
230
     * @param array $options Request options
231
     * @return \Cake\Http\Client\Response
232
     */
233
    public function patch(string $url, array $data = [], array $options = []): Response
234
    {
235
        $apiUrl = $this->getUrl($url);
236
        $data = json_encode($data);
237
        $response = $this->client->patch($apiUrl, $data, $options);
238
        $this->logCall('/PATCH', $apiUrl, $data, $response);
239
240
        return $response;
241
    }
242
243
    /**
244
     * Put request.
245
     *
246
     * @param string $url The request url
247
     * @param array $data The post data
248
     * @param array $options Request options
249
     * @return \Cake\Http\Client\Response
250
     */
251
    public function put(string $url, array $data = [], array $options = []): Response
252
    {
253
        $apiUrl = $this->getUrl($url);
254
        $data = json_encode($data);
255
        $response = $this->client->put($this->getUrl($url), $data, $options);
256
        $this->logCall('/PUT', $apiUrl, $data, $response);
257
258
        return $response;
259
    }
260
261
    /**
262
     * Delete request.
263
     *
264
     * @param string $url The request url
265
     * @param array $data The post data
266
     * @param array $options Request options
267
     * @return \Cake\Http\Client\Response
268
     */
269
    public function delete(string $url, array $data = [], array $options = []): Response
270
    {
271
        $apiUrl = $this->getUrl($url);
272
        $data = json_encode($data);
273
        $response = $this->client->delete($this->getUrl($url), $data, $options);
274
        $this->logCall('/DELETE', $apiUrl, $data, $response);
275
276
        return $response;
277
    }
278
}
279