Passed
Pull Request — master (#111)
by Dane
02:49 queued 16s
created

Client   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 252
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 78
dl 0
loc 252
rs 10
c 1
b 0
f 0
wmc 23

15 Methods

Rating   Name   Duplication   Size   Complexity  
A clearOptions() 0 3 1
A clearQuery() 0 3 1
A getQuery() 0 3 1
A addOption() 0 3 1
A getOptions() 0 3 1
A addQuery() 0 3 1
A factory() 0 7 1
A __construct() 0 3 1
A request() 0 13 1
A getVersion() 0 3 1
A modifyOptions() 0 29 5
A processResponse() 0 11 3
A stream() 0 13 1
A getLinkedResource() 0 43 2
A makeRequest() 0 9 2
1
<?php
2
3
namespace AcquiaCloudApi\Connector;
4
5
use Psr\Http\Message\ResponseInterface;
6
use GuzzleHttp\Exception\BadResponseException;
7
use AcquiaCloudApi\Exception\ApiErrorException;
8
use Psr\Http\Message\StreamInterface;
9
use AcquiaCloudApi\Exception\LinkedResourceNotImplementedException;
10
11
/**
12
 * Class Client
13
 *
14
 * @package AcquiaCloudApi\CloudApi
15
 */
16
class Client implements ClientInterface
17
{
18
    /**
19
     * @var ConnectorInterface The API connector.
20
     */
21
    protected $connector;
22
23
    /**
24
     * @var array<string, mixed> Query strings to be applied to the request.
25
     */
26
    protected $query = [];
27
28
    /**
29
     * @var array<string, mixed> Guzzle options to be applied to the request.
30
     */
31
    protected $options = [];
32
33
    /**
34
     * @var array<string, mixed> Request options from each individual API call.
35
     */
36
    private $requestOptions = [];
37
38
    /**
39
     * Client constructor.
40
     *
41
     * @param ConnectorInterface $connector
42
     */
43
    final public function __construct(ConnectorInterface $connector)
44
    {
45
        $this->connector = $connector;
46
    }
47
48
    /**
49
     * Client factory method for instantiating.
50
     *
51
     * @param ConnectorInterface $connector
52
     *
53
     * @return static
54
     */
55
    public static function factory(ConnectorInterface $connector)
56
    {
57
        $client = new static(
58
            $connector
59
        );
60
61
        return $client;
62
    }
63
64
    /**
65
     * @inheritdoc
66
     */
67
    public function getVersion(): string
68
    {
69
        return self::VERSION;
70
    }
71
72
    /**
73
     * @inheritdoc
74
     */
75
    public function modifyOptions(): array
76
    {
77
        // Combine options set globally e.g. headers with options set by individual API calls e.g. form_params.
78
        $options = $this->options + $this->requestOptions;
79
80
        // This library can be standalone or as a dependency. Dependent libraries may also set their own user agent
81
        // which will make $options['headers']['User-Agent'] an array.
82
        // We need to array_unique() the array of User-Agent headers as multiple calls may include multiple of the same header.
83
        // We also use array_unshift() to place this library's user agent first to order to have it appear at the beginning of log files.
84
        // As Guzzle joins arrays with a comma, we must implode with a space here to pass Guzzle a string.
85
        $userAgent = sprintf(
86
            "%s/%s (https://github.com/typhonius/acquia-php-sdk-v2)",
87
            'acquia-php-sdk-v2',
88
            $this->getVersion()
89
        );
90
        if (isset($options['headers']['User-Agent']) && is_array($options['headers']['User-Agent'])) {
91
            array_unshift($options['headers']['User-Agent'], $userAgent);
92
            $options['headers']['User-Agent'] = implode(' ', array_unique($options['headers']['User-Agent']));
93
        } else {
94
            $options['headers']['User-Agent'] = $userAgent;
95
        }
96
97
        $options['query'] = $this->query;
98
        if (!empty($options['query']['filter']) && is_array($options['query']['filter'])) {
99
            // Default to an OR filter to increase returned responses.
100
            $options['query']['filter'] = implode(',', $options['query']['filter']);
101
        }
102
103
        return $options;
104
    }
105
106
    /**
107
     * @inheritdoc
108
     */
109
    public function request(string $verb, string $path, array $options = [])
110
    {
111
        // Put options sent with API calls into a property so they can be accessed
112
        // and therefore tested in tests.
113
        $this->requestOptions = $options;
114
115
        // Modify the options to combine options set as part of the API call as well
116
        // as those set by tools extending this library.
117
        $modifiedOptions = $this->modifyOptions();
118
119
        $response = $this->makeRequest($verb, $path, $modifiedOptions);
120
121
        return $this->processResponse($response);
122
    }
123
124
    /**
125
     * @inheritdoc
126
     */
127
    public function stream(string $verb, string $path, array $options = [])
128
    {
129
        // Put options sent with API calls into a property so they can be accessed
130
        // and therefore tested in tests.
131
        $this->requestOptions = $options;
132
133
        // Modify the options to combine options set as part of the API call as well
134
        // as those set by tools extending this library.
135
        $modifiedOptions = $this->modifyOptions();
136
137
        $response = $this->makeRequest($verb, $path, $modifiedOptions);
138
139
        return $response->getBody();
140
    }
141
142
    /**
143
     * @inheritdoc
144
     */
145
    public function makeRequest(string $verb, string $path, array $options = []): ResponseInterface
146
    {
147
        try {
148
            $response = $this->connector->sendRequest($verb, $path, $options);
149
        } catch (BadResponseException $e) {
150
            $response = $e->getResponse();
151
        }
152
153
        return $response;
154
    }
155
156
    /**
157
     * @inheritdoc
158
     */
159
    public function processResponse(ResponseInterface $response)
160
    {
161
162
        $body_json = $response->getBody();
163
        $body = json_decode($body_json);
164
165
        if (property_exists($body, 'error') && property_exists($body, 'message')) {
166
            throw new ApiErrorException($body);
167
        }
168
169
        return $body;
170
    }
171
172
    /**
173
     * @inheritdoc
174
     */
175
    public function getQuery(): array
176
    {
177
        return $this->query;
178
    }
179
180
    /**
181
     * @inheritdoc
182
     */
183
    public function clearQuery(): void
184
    {
185
        $this->query = [];
186
    }
187
188
    /**
189
     * @inheritdoc
190
     */
191
    public function addQuery($name, $value): void
192
    {
193
        $this->query = array_merge_recursive($this->query, [$name => $value]);
194
    }
195
196
    /**
197
     * @inheritdoc
198
     */
199
    public function getOptions(): array
200
    {
201
        return $this->options;
202
    }
203
204
    /**
205
     * @inheritdoc
206
     */
207
    public function clearOptions(): void
208
    {
209
        $this->options = [];
210
    }
211
212
    /**
213
     * @inheritdoc
214
     */
215
    public function addOption($name, $value): void
216
    {
217
        $this->options = array_merge_recursive($this->options, [$name => $value]);
218
    }
219
220
    /**
221
     * @param array{type:string, path:string, responseClass:class-string} $link
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{type:string, path:...onseClass:class-string} at position 12 could not be parsed: Unknown type name 'class-string' at position 12 in array{type:string, path:string, responseClass:class-string}.
Loading history...
222
     * @return mixed
223
     * @throws LinkedResourceNotImplementedException
224
     */
225
    public function getLinkedResource($link)
226
    {
227
        // Remove the base URI from the path as this is already added by the Connector when we call request().
228
        $path = str_replace(ConnectorInterface::BASE_URI, '', $link['path']);
229
        $type = $link['type'];
230
        $responseClass = $link['responseClass'];
231
232
        $classMap = [
233
            'alerts' => '\AcquiaCloudApi\Response\InsightAlertsResponse',
234
            'applications' => '\AcquiaCloudApi\Response\ApplicationsResponse',
235
            'backups' => '\AcquiaCloudApi\Response\BackupsResponse',
236
            'code' => '\AcquiaCloudApi\Response\BranchesResponse',
237
            'crons' => '\AcquiaCloudApi\Response\CronsResponse',
238
            'databases' => '\AcquiaCloudApi\Response\DatabasesResponse',
239
            'domains' => '\AcquiaCloudApi\Response\DomainsResponse',
240
            'environments' => '\AcquiaCloudApi\Response\EnvironmentsResponse',
241
            'ides' => '\AcquiaCloudApi\Response\IdesResponse',
242
            'insight' => '\AcquiaCloudApi\Response\InsightsResponse',
243
            'logs' => '\AcquiaCloudApi\Response\LogsResponse',
244
            'members' => '\AcquiaCloudApi\Response\MembersResponse',
245
            'metrics' => '\AcquiaCloudApi\Response\MetricsResponse',
246
            'modules' => '\AcquiaCloudApi\Response\InsightModulesResponse',
247
            'notification' => '\AcquiaCloudApi\Response\NotificationResponse',
248
            'permissions' => '\AcquiaCloudApi\Response\PermissionsResponse',
249
            'self' => $responseClass,
250
            'servers' => '\AcquiaCloudApi\Response\ServersResponse',
251
            'ssl' => '\AcquiaCloudApi\Response\SslCertificatesResponse',
252
            'teams' => '\AcquiaCloudApi\Response\TeamsResponse',
253
            'variables' => '\AcquiaCloudApi\Response\VariablesResponse',
254
        ];
255
256
        // Clear any queries attached to the client to prevent sorts etc being carried through
257
        // from the original query.
258
        // @TODO this may need to be removed if users wish to filter/sort linked resources.
259
        $this->clearQuery();
260
261
        if (isset($classMap[$type])) {
262
            return new $classMap[$type](
263
                $this->request('get', $path)
264
            );
265
        }
266
267
        throw new LinkedResourceNotImplementedException($type . ' link not implemented in this SDK. Please file an issue here: https://github.com/typhonius/acquia-php-sdk-v2/issues');
268
    }
269
}
270