GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 70b9c4...5cb4fc )
by Charlotte
01:53
created

Client::quote()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.3332

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 2
dl 0
loc 15
ccs 6
cts 9
cp 0.6667
crap 3.3332
rs 9.9666
c 0
b 0
f 0
1
<?php
2
/**
3
 * Plasma Core component
4
 * Copyright 2018-2019 PlasmaPHP, All Rights Reserved
5
 *
6
 * Website: https://github.com/PlasmaPHP
7
 * License: https://github.com/PlasmaPHP/core/blob/master/LICENSE
8
*/
9
10
namespace Plasma;
11
12
/**
13
 * The plasma client, responsible for pooling and connections.
14
 */
15
class Client implements ClientInterface {
16
    use \Evenement\EventEmitterTrait;
17
    
18
    /**
19
     * @var \Plasma\DriverFactoryInterface
20
     */
21
    protected $factory;
22
    
23
    /**
24
     * @var string
25
     */
26
    protected $uri;
27
    
28
    /**
29
     * @var array
30
     */
31
    protected $options = array(
32
        'connections.max' => 5,
33
        'connections.lazy' => false
34
    );
35
    
36
    /**
37
     * @var \React\Promise\PromiseInterface
38
     */
39
    protected $goingAway;
40
    
41
    /**
42
     * @var \CharlotteDunois\Collect\Set
43
     */
44
    protected $connections;
45
    
46
    /**
47
     * @var \CharlotteDunois\Collect\Set
48
     */
49
    protected $busyConnections;
50
    
51
    /**
52
     * Creates a client with the specified factory and options.
53
     *
54
     * Available options:
55
     * ```
56
     * array(
57
     *     'connections.max' => int, (the maximum amount of connections to open, defaults to 5)
58
     *     'connections.lazy' => bool, (whether the first connection should be established lazily (on first request), defaults to false)
59
     * )
60
     * ```
61
     *
62
     * @param \Plasma\DriverFactoryInterface  $factory
63
     * @param string                          $uri
64
     * @param array                           $options
65
     * @throws \InvalidArgumentException
66
     */
67 52
    function __construct(\Plasma\DriverFactoryInterface $factory, string $uri, array $options = array()) {
68 52
        $this->validateOptions($options);
69
        
70 52
        $this->factory = $factory;
71 52
        $this->uri = $uri;
72 52
        $this->options = \array_merge($this->options, $options);
73
        
74 52
        $this->connections = new \CharlotteDunois\Collect\Set();
75 52
        $this->busyConnections = new \CharlotteDunois\Collect\Set();
76
        
77 52
        if(!$this->options['connections.lazy']) {
78 27
            $connection = $this->createNewConnection();
79 27
            if($connection->getConnectionState() !== \Plasma\DriverInterface::CONNECTION_OK) {
80
                $this->busyConnections->add($connection);
81
            }
82
        }
83 52
    }
84
    
85
    /**
86
     * Creates a client with the specified factory and options.
87
     * @param \Plasma\DriverFactoryInterface  $factory
88
     * @param string                          $uri
89
     * @param array                           $options
90
     * @return \Plasma\ClientInterface
91
     * @throws \Throwable  The client implementation may throw any exception during this operation.
92
     * @see Client::__construct()
93
     */
94 52
    static function create(\Plasma\DriverFactoryInterface $factory, string $uri, array $options = array()): \Plasma\ClientInterface {
95 52
        return (new static($factory, $uri, $options));
96
    }
97
    
98
    /**
99
     * Get the amount of connections.
100
     * @return int
101
     */
102 4
    function getConnectionCount(): int {
103 4
        return ($this->connections->count() + $this->busyConnections->count());
104
    }
105
    
106
    /**
107
     * Checks a connection back in, if usable and not closing.
108
     * @param \Plasma\DriverInterface  $driver
109
     * @return void
110
     */
111 15
    function checkinConnection(\Plasma\DriverInterface $driver): void {
112 15
        if($driver->getConnectionState() !== \Plasma\DriverInterface::CONNECTION_UNUSABLE && !$this->goingAway) {
113 7
            $this->connections->add($driver);
114 7
            $this->busyConnections->delete($driver);
115
        }
116 15
    }
117
    
118
    /**
119
     * Begins a transaction. Resolves with a `Transaction` instance.
120
     *
121
     * Checks out a connection until the transaction gets committed or rolled back. If the transaction goes out of scope
122
     * and thus deallocated, the `Transaction` must check the connection back into the client.
123
     *
124
     * Some databases, including MySQL, automatically issue an implicit COMMIT when a database definition language (DDL)
125
     * statement such as DROP TABLE or CREATE TABLE is issued within a transaction.
126
     * The implicit COMMIT will prevent you from rolling back any other changes within the transaction boundary.
127
     * @param int  $isolation  See the `TransactionInterface` constants.
128
     * @return \React\Promise\PromiseInterface
129
     * @throws \Plasma\Exception
130
     * @see \Plasma\Transaction
131
     */
132 3
    function beginTransaction(int $isolation = \Plasma\TransactionInterface::ISOLATION_COMMITTED): \React\Promise\PromiseInterface {
133 3
        if($this->goingAway) {
134 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
135
        }
136
        
137 2
        $connection = $this->getOptimalConnection();
138
        
139
        return $connection->beginTransaction($this, $isolation)->then(null, function (\Throwable $error) use (&$connection) {
140 1
            $this->checkinConnection($connection);
141 1
            throw $error;
142 2
        });
143
    }
144
    
145
    /**
146
     * Executes a plain query. Resolves with a `QueryResult` instance.
147
     * @param string  $query
148
     * @return \React\Promise\PromiseInterface
149
     * @see \Plasma\QueryResultInterface
150
     */
151 5
    function query(string $query): \React\Promise\PromiseInterface {
152 5
        if($this->goingAway) {
153 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
154
        }
155
        
156 4
        $connection = $this->getOptimalConnection();
157
        
158
        return $connection->query($this, $query)->then(null, function (\Throwable $error) use (&$connection) {
159 1
            $this->checkinConnection($connection);
160 1
            throw $error;
161 4
        });
162
    }
163
    
164
    /**
165
     * Prepares a query. Resolves with a `StatementInterface` instance.
166
     * @param string  $query
167
     * @return \React\Promise\PromiseInterface
168
     * @see \Plasma\StatementInterface
169
     */
170 3
    function prepare(string $query): \React\Promise\PromiseInterface {
171 3
        if($this->goingAway) {
172 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
173
        }
174
        
175 2
        $connection = $this->getOptimalConnection();
176
        
177
        return $connection->prepare($this, $query)->then(null, function (\Throwable $error) use (&$connection) {
178 1
            $this->checkinConnection($connection);
179 1
            throw $error;
180 2
        });
181
    }
182
    
183
    /**
184
     * Prepares and executes a query. Resolves with a `QueryResultInterface` instance.
185
     * This is equivalent to prepare -> execute -> close.
186
     * If you need to execute a query multiple times, prepare the query manually for performance reasons.
187
     * @param string  $query
188
     * @param array   $params
189
     * @return \React\Promise\PromiseInterface
190
     * @throws \Plasma\Exception
191
     * @see \Plasma\StatementInterface
192
     */
193 3
    function execute(string $query, array $params = array()): \React\Promise\PromiseInterface {
194 3
        if($this->goingAway) {
195 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
196
        }
197
        
198 2
        $connection = $this->getOptimalConnection();
199
        
200
        return $connection->execute($this, $query, $params)->then(function ($value) use (&$connection) {
201 1
            $this->checkinConnection($connection);
202 1
            return $value;
203
        }, function (\Throwable $error) use (&$connection) {
204 1
            $this->checkinConnection($connection);
205 1
            throw $error;
206 2
        });
207
    }
208
    
209
    /**
210
     * Quotes the string for use in the query.
211
     * @param string  $str
212
     * @param int     $type  For types, see the driver interface constants.
213
     * @return string
214
     * @throws \LogicException    Thrown if the driver does not support quoting.
215
     * @throws \Plasma\Exception  Thrown if the client is closing all connections.
216
     */
217 2
    function quote(string $str, int $type = \Plasma\DriverInterface::QUOTE_TYPE_VALUE): string {
218 2
        if($this->goingAway) {
219 1
            throw new \Plasma\Exception('Client is closing all connections');
220
        }
221
        
222 1
        $connection = $this->getOptimalConnection();
223
        
224
        try {
225 1
            $quoted = $connection->quote($str, $type);
226
        } catch (\Throwable $e) {
227
            $this->checkinConnection($connection);
228
            throw $e;
229
        }
230
        
231 1
        return $quoted;
232
    }
233
    
234
    /**
235
     * Closes all connections gracefully after processing all outstanding requests.
236
     * @return \React\Promise\PromiseInterface
237
     */
238 3
    function close(): \React\Promise\PromiseInterface {
239 3
        if($this->goingAway) {
240 1
            return $this->goingAway;
241
        }
242
        
243 2
        $deferred = new \React\Promise\Deferred();
244 2
        $this->goingAway = $deferred->promise();
245
        
246 2
        $closes = array();
247
        
248
        /** @var \Plasma\DriverInterface  $conn */
249 2
        foreach($this->connections->all() as $conn) {
250 1
            $closes[] = $conn->close();
251 1
            $this->connections->delete($conn);
252
        }
253
        
254
        /** @var \Plasma\DriverInterface  $conn */
255 2
        foreach($this->busyConnections->all() as $conn) {
256 1
            $closes[] = $conn->close();
257 1
            $this->busyConnections->delete($conn);
258
        }
259
        
260 2
        \React\Promise\all($closes)->then(array($deferred, 'resolve'), array($deferred, 'reject'));
261 2
        return $this->goingAway;
262
    }
263
    
264
    /**
265
     * Forcefully closes the connection, without waiting for any outstanding requests. This will reject all oustanding requests.
266
     * @return void
267
     */
268 10
    function quit(): void {
269 10
        if($this->goingAway) {
270 1
            return;
271
        }
272
        
273 9
        $this->goingAway = \React\Promise\resolve();
274
        
275
        /** @var \Plasma\DriverInterface  $conn */
276 9
        foreach($this->connections->all() as $conn) {
277 8
            $conn->quit();
278 8
            $this->connections->delete($conn);
279
        }
280
        
281
        /** @var \Plasma\DriverInterface  $conn */
282 9
        foreach($this->busyConnections->all() as $conn) {
283 1
            $conn->quit();
284 1
            $this->busyConnections->delete($conn);
285
        }
286 9
    }
287
    
288
    /**
289
     * Runs the given command.
290
     * @param \Plasma\CommandInterface  $command
291
     * @return mixed  Return depends on command and driver.
292
     * @throws \Plasma\Exception  Thrown if the client is closing all connections.
293
     */
294 3
    function runCommand(\Plasma\CommandInterface $command) {
295 3
        if($this->goingAway) {
296 1
            throw new \Plasma\Exception('Client is closing all connections');
297
        }
298
        
299 2
        $connection = $this->getOptimalConnection();
300
        
301
        try {
302 2
            return $connection->runCommand($this, $command);
303
        } catch (\Throwable $e) {
304
            $this->checkinConnection($connection);
305
            throw $e;
306
        }
307
    }
308
    
309
    /**
310
     * Runs the given querybuilder on an underlying driver instance.
311
     * The driver CAN throw an exception if the given querybuilder is not supported.
312
     * An example would be a SQL querybuilder and a Cassandra driver.
313
     * @param \Plasma\QuerybuilderInterface  $query
314
     * @return \React\Promise\PromiseInterface
315
     * @throws \Plasma\Exception
316
     */
317 2
    function runQuery(\Plasma\QuerybuilderInterface $query): \React\Promise\PromiseInterface {
318 2
        if($this->goingAway) {
319 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
320
        }
321
        
322 1
        $connection = $this->getOptimalConnection();
323
        
324
        try {
325 1
            return $connection->runQuery($this, $query);
326
        } catch (\Throwable $e) {
327
            $this->checkinConnection($connection);
328
            throw $e;
329
        }
330
    }
331
    
332
    /**
333
     * Get the optimal connection.
334
     * @return \Plasma\DriverInterface
335
     */
336 14
    protected function getOptimalConnection(): \Plasma\DriverInterface {
337 14
        if(\count($this->connections) === 0) {
338 1
            $connection = $this->createNewConnection();
339 1
            $this->busyConnections->add($connection);
340
            
341 1
            return $connection;
342
        }
343
        
344
        /** @var \Plasma\DriverInterface  $connection */
345 13
        $this->connections->rewind();
346 13
        $connection = $this->connections->current();
347
        
348 13
        $backlog = $connection->getBacklogLength();
349 13
        $state = $connection->getBusyState();
350
        
351
        /** @var \Plasma\DriverInterface  $conn */
352 13
        foreach($this->connections as $conn) {
353 13
            $cbacklog = $conn->getBacklogLength();
354 13
            $cstate = $conn->getBusyState();
355
            
356 13
            if($cbacklog === 0 && $conn->getConnectionState() === \Plasma\DriverInterface::CONNECTION_OK && $cstate == \Plasma\DriverInterface::STATE_IDLE) {
357 13
                $this->connections->delete($conn);
358 13
                $this->busyConnections->add($conn);
359
                
360 13
                return $conn;
361
            }
362
            
363
            if($backlog > $cbacklog || $state > $cstate) {
364
                $connection = $conn;
365
                $backlog = $cbacklog;
366
                $state = $cstate;
367
            }
368
        }
369
        
370
        if($this->getConnectionCount() < $this->options['connections.max']) {
371
            $connection = $this->createNewConnection();
372
        }
373
        
374
        $this->connections->delete($connection);
375
        $this->busyConnections->add($connection);
376
        
377
        return $connection;
378
    }
379
    
380
    /**
381
     * Create a new connection.
382
     * @return \Plasma\DriverInterface
383
     */
384 28
    protected function createNewConnection(): \Plasma\DriverInterface {
385 28
        $connection = $this->factory->createDriver();
386
        
387
        // We relay a driver's specific events forward, e.g. PostgreSQL notifications
388
        $connection->on('eventRelay', function (string $eventName, ...$args) use (&$connection) {
389 1
            $args[] = $connection;
390 1
            $this->emit($eventName, $args);
391 28
        });
392
        
393
        $connection->on('close', function () use (&$connection) {
394 2
            $this->connections->delete($connection);
395 2
            $this->busyConnections->delete($connection);
396
            
397 2
            $this->emit('close', array($connection));
398 28
        });
399
        
400
        $connection->on('error', function (\Throwable $error) use (&$connection) {
401 1
            $this->emit('error', array($error, $connection));
402 28
        });
403
        
404
        $connection->connect($this->uri)->then(function () use (&$connection) {
405 28
            $this->connections->add($connection);
406 28
            $this->busyConnections->delete($connection);
407
        }, function (\Throwable $error) use (&$connection) {
408
            $this->connections->delete($connection);
409
            $this->busyConnections->delete($connection);
410
            
411
            $this->emit('error', array($error, $connection));
412 28
        });
413
        
414 28
        return $connection;
415
    }
416
    
417
    /**
418
     * Validates the given options.
419
     * @param array  $options
420
     * @return void
421
     * @throws \InvalidArgumentException
422
     */
423 52
    protected function validateOptions(array $options) {
424 52
        $validator = \CharlotteDunois\Validation\Validator::make($options, array(
425 52
            'connections.max' => 'integer|min:1',
426
            'connections.lazy' => 'boolean'
427
        ));
428
        
429 52
        $validator->throw(\InvalidArgumentException::class);
430 52
    }
431
}
432