Failed Conditions
Push — next ( ee030a...ae385d )
by Bas
02:43
created

ArangoClient::__construct()   B

Complexity

Conditions 8
Paths 128

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 8

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 8
nc 128
nop 2
dl 0
loc 11
ccs 9
cts 9
cp 1
crap 8
rs 8.2111
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ArangoClient;
6
7
use ArangoClient\Admin\AdminManager;
8
use ArangoClient\Exceptions\ArangoException;
9
use ArangoClient\Schema\SchemaManager;
10
use ArangoClient\Statement\Statement;
11
use GuzzleHttp\Client;
12
use GuzzleHttp\Exception\RequestException;
13
use GuzzleHttp\Psr7\StreamWrapper;
14
use JsonMachine\JsonMachine;
15
use Psr\Http\Message\ResponseInterface;
16
use Throwable;
17
use Traversable;
18
19
/**
20
 * The arangoClient handles connections to ArangoDB's HTTP REST API.
21
 *
22
 * @see https://www.arangodb.com/docs/stable/http/
23
 */
24
class ArangoClient
25
{
26
27
    protected Client $httpClient;
28
29
    /**
30
     * @var string
31
     */
32
    protected string $endpoint;
33
34
    /**
35
     * @var array<mixed>|false
36
     */
37
    protected $allowRedirects;
38
39
    /**
40
     * @var float
41
     */
42
    protected float $connectTimeout;
43
44
    /**
45
     * @var string
46
     */
47
    protected string $connection;
48
49
    /**
50
     * @var string|null
51
     */
52
    protected ?string $username = null;
53
54
    /**
55
     * @var string|null
56
     */
57
    protected ?string $password = null;
58
59
    /**
60
     * @var string
61
     */
62
    protected string $database;
63
64
    /**
65
     * @var SchemaManager|null
66
     */
67
    protected ?SchemaManager $schemaManager = null;
68
69
    /**
70
     * @var AdminManager|null
71
     */
72
    protected ?AdminManager $adminManager = null;
73
74
    /**
75
     * ArangoClient constructor.
76
     *
77
     * @param  array<string|numeric|null>  $config
78
     * @param  Client|null  $httpClient
79
     */
80 58
    public function __construct(array $config = [], Client $httpClient = null)
81
    {
82 58
        $this->endpoint = $this->generateEndpoint($config);
83 58
        $this->username = (isset($config['username'])) ? (string) $config['username'] : null;
84 58
        $this->password = (isset($config['password'])) ? (string) $config['password'] : null;
85 58
        $this->database = (isset($config['database'])) ? (string) $config['database'] : '_system';
86 58
        $this->connection = (isset($config['connection'])) ? (string) $config['connection'] : 'Keep-Alive';
87 58
        $this->allowRedirects = (isset($config['allow_redirects'])) ? (array) $config['allow_redirects'] : false;
88 58
        $this->connectTimeout = (isset($config['connect_timeout'])) ? (float) $config['connect_timeout'] : 0;
89
90 58
        $this->httpClient = isset($httpClient) ? $httpClient : new Client($this->mapHttpClientConfig());
91 58
    }
92
93
    /**
94
     * @param  array<mixed>  $config
95
     * @return string
96
     */
97 58
    public function generateEndpoint(array $config): string
98
    {
99 58
        if (isset($config['endpoint'])) {
100
            return (string) $config['endpoint'];
101
        }
102
103 58
        $endpoint = (isset($config['host'])) ? (string) $config['host'] : 'http://localhost';
104 58
        $endpoint .= (isset($config['port'])) ? ':' . (string) $config['port'] : ':8529';
105
106 58
        return $endpoint;
107
    }
108
109
    /**
110
     * @return array<array<mixed>|string|numeric|bool|null>
111
     */
112 58
    protected function mapHttpClientConfig(): array
113
    {
114 58
        $config = [];
115 58
        $config['base_uri'] = $this->endpoint;
116 58
        $config['allow_redirects'] = $this->allowRedirects;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->allowRedirects can also be of type boolean. However, the property $allowRedirects is declared as type array<mixed,mixed>|false. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
117 58
        $config['connect_timeout'] = $this->connectTimeout;
118 58
        $config['auth'] = [
119 58
            $this->username,
120 58
            $this->password,
121
        ];
122 58
        $config['header'] = [
123 58
            'Connection' => $this->connection
124
        ];
125 58
        return $config;
126
    }
127
128
    /**
129
     * @psalm-suppress MixedReturnStatement
130
     *
131
     * @param  string  $method
132
     * @param  string  $uri
133
     * @param  array<mixed>  $options
134
     * @return array<mixed>
135
     * @throws ArangoException
136
     */
137 58
    public function request(string $method, string $uri, array $options = []): array
138
    {
139 58
        $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
140
        try {
141 58
            $response = $this->httpClient->request($method, $uri, $options);
142 1
        } catch (\Throwable $e) {
143 1
            $this->handleGuzzleException($e);
144
        }
145
146 58
        return $this->decodeResponse($response);
147
    }
148
149
    /**
150
     * @return array<array<mixed>|string|numeric|bool|null>
151
     */
152 1
    public function getConfig(): array
153
    {
154 1
        $config = [];
155 1
        $config['endpoint'] = $this->endpoint;
156 1
        $config['username'] = $this->username;
157 1
        $config['password'] = $this->password;
158 1
        $config['database'] = $this->database;
159 1
        $config['connection'] = $this->connection;
160 1
        $config['allow_redirects'] = $this->allowRedirects;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->allowRedirects can also be of type boolean. However, the property $allowRedirects is declared as type array<mixed,mixed>|false. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
161 1
        $config['connect_timeout'] = $this->connectTimeout;
162
163 1
        return $config;
164
    }
165
166
    /**
167
     * @psalm-suppress MixedAssignment, MixedArrayOffset
168
     * @SuppressWarnings(PHPMD.StaticAccess)
169
     *
170
     * @param  ResponseInterface|null  $response
171
     * @return array<mixed>
172
     */
173 58
    protected function decodeResponse(?ResponseInterface $response): array
174
    {
175 58
        if (! isset($response)) {
176
            return [];
177
        }
178
179 58
        $decodedResponse = [];
180
181 58
        $phpStream = StreamWrapper::getResource($response->getBody());
182 58
        $decodedStream = JsonMachine::fromStream($phpStream);
183
184 58
        foreach ($decodedStream as $key => $value) {
185 58
            $decodedResponse[$key] = $value;
186
        }
187
188 58
        return $decodedResponse;
189
    }
190
191
    /**
192
     * @param  array<mixed>  $data
193
     * @return string
194
     */
195 10
    public function jsonEncode(array $data): string
196
    {
197 10
        return (string) json_encode($data, JSON_FORCE_OBJECT);
198
    }
199
200
    /**
201
     * @return string
202
     */
203 58
    public function getUser(): string
204
    {
205 58
        return (string) $this->username;
206
    }
207
208
    /**
209
     * @param string $name
210
     * @return void
211
     */
212 1
    public function setDatabase(string $name): void
213
    {
214 1
        $this->database = $name;
215 1
    }
216
217
    /**
218
     * @return string
219
     */
220 1
    public function getDatabase(): string
221
    {
222 1
        return $this->database;
223
    }
224
225
    /**
226
     * @param  string  $query
227
     * @param  array<scalar>  $bindVars
228
     * @param  array<array<string>>  $collections
229
     * @param  array<mixed>  $options
230
     * @return Traversable<mixed>
231
     */
232 12
    public function prepare(
233
        string $query,
234
        array $bindVars = [],
235
        array $collections = [],
236
        array $options = []
237
    ): Traversable {
238 12
        return new Statement($this, $query, $bindVars, $collections, $options);
239
    }
240
241
    /**
242
     * @return SchemaManager
243
     */
244 1
    public function schema(): SchemaManager
245
    {
246 1
        if (! isset($this->schemaManager)) {
247 1
            $this->schemaManager = new SchemaManager($this);
248
        }
249 1
        return $this->schemaManager;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->schemaManager could return the type null which is incompatible with the type-hinted return ArangoClient\Schema\SchemaManager. Consider adding an additional type-check to rule them out.
Loading history...
250
    }
251
    /**
252
     * @return AdminManager
253
     */
254 1
    public function admin(): AdminManager
255
    {
256 1
        if (! isset($this->adminManager)) {
257 1
            $this->adminManager = new AdminManager($this);
258
        }
259 1
        return $this->adminManager;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->adminManager could return the type null which is incompatible with the type-hinted return ArangoClient\Admin\AdminManager. Consider adding an additional type-check to rule them out.
Loading history...
260
    }
261
262
    /**
263
     * @param  Throwable  $e
264
     * @throws ArangoException
265
     */
266 1
    private function handleGuzzleException(Throwable $e): void
267
    {
268 1
        $message = $e->getMessage();
269 1
        $code = $e->getCode();
270 1
        if ($e instanceof RequestException && $e->hasResponse()) {
271 1
            $decodedResponse = $this->decodeResponse($e->getResponse());
272 1
            $message = (string) $decodedResponse['errorMessage'];
273 1
            $code = (int) $decodedResponse['code'];
274
        }
275
276
        throw(
277 1
            new ArangoException(
278 1
                $message,
279 1
                (int) $code
280
            )
281
        );
282
    }
283
}
284