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 ( 45007f...b2e0bf )
by Charlotte
07:09
created

Client::createNewConnection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 1.0046

Importance

Changes 0
Metric Value
cc 1
eloc 18
nc 1
nop 0
dl 0
loc 31
ccs 15
cts 18
cp 0.8333
crap 1.0046
rs 9.6666
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 16
    function checkinConnection(\Plasma\DriverInterface $driver): void {
112 16
        if($driver->getConnectionState() !== \Plasma\DriverInterface::CONNECTION_UNUSABLE && !$this->goingAway) {
113 8
            $this->connections->add($driver);
114 8
            $this->busyConnections->delete($driver);
115
        }
116 16
    }
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 1
        $quoted = $connection->quote($str, $type);
224
        
225 1
        $this->checkinConnection($connection);
226 1
        return $quoted;
227
    }
228
    
229
    /**
230
     * Closes all connections gracefully after processing all outstanding requests.
231
     * @return \React\Promise\PromiseInterface
232
     */
233 3
    function close(): \React\Promise\PromiseInterface {
234 3
        if($this->goingAway) {
235 1
            return $this->goingAway;
236
        }
237
        
238 2
        $deferred = new \React\Promise\Deferred();
239 2
        $this->goingAway = $deferred->promise();
240
        
241 2
        $closes = array();
242
        
243
        /** @var \Plasma\DriverInterface  $conn */
244 2
        foreach($this->connections->all() as $conn) {
245 1
            $closes[] = $conn->close();
246 1
            $this->connections->delete($conn);
247
        }
248
        
249
        /** @var \Plasma\DriverInterface  $conn */
250 2
        foreach($this->busyConnections->all() as $conn) {
251 1
            $closes[] = $conn->close();
252 1
            $this->busyConnections->delete($conn);
253
        }
254
        
255 2
        \React\Promise\all($closes)->then(array($deferred, 'resolve'), array($deferred, 'reject'));
256 2
        return $this->goingAway;
257
    }
258
    
259
    /**
260
     * Forcefully closes the connection, without waiting for any outstanding requests. This will reject all oustanding requests.
261
     * @return void
262
     */
263 10
    function quit(): void {
264 10
        if($this->goingAway) {
265 1
            return;
266
        }
267
        
268 9
        $this->goingAway = \React\Promise\resolve();
269
        
270
        /** @var \Plasma\DriverInterface  $conn */
271 9
        foreach($this->connections->all() as $conn) {
272 8
            $conn->quit();
273 8
            $this->connections->delete($conn);
274
        }
275
        
276
        /** @var \Plasma\DriverInterface  $conn */
277 9
        foreach($this->busyConnections->all() as $conn) {
278 1
            $conn->quit();
279 1
            $this->busyConnections->delete($conn);
280
        }
281 9
    }
282
    
283
    /**
284
     * Runs the given command.
285
     * @param \Plasma\CommandInterface  $command
286
     * @return mixed  Return depends on command and driver.
287
     * @throws \Plasma\Exception  Thrown if the client is closing all connections.
288
     */
289 3
    function runCommand(\Plasma\CommandInterface $command) {
290 3
        if($this->goingAway) {
291 1
            throw new \Plasma\Exception('Client is closing all connections');
292
        }
293
        
294 2
        $connection = $this->getOptimalConnection();
295 2
        return $connection->runCommand($this, $command);
296
    }
297
    
298
    /**
299
     * Runs the given querybuilder on an underlying driver instance.
300
     * The driver CAN throw an exception if the given querybuilder is not supported.
301
     * An example would be a SQL querybuilder and a Cassandra driver.
302
     * @param \Plasma\QuerybuilderInterface  $query
303
     * @return \React\Promise\PromiseInterface
304
     * @throws \Plasma\Exception
305
     */
306 2
    function runQuery(\Plasma\QuerybuilderInterface $query): \React\Promise\PromiseInterface {
307 2
        if($this->goingAway) {
308 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
309
        }
310
        
311 1
        $connection = $this->getOptimalConnection();
312 1
        return $connection->runQuery($this, $query);
313
    }
314
    
315
    /**
316
     * Get the optimal connection.
317
     * @return \Plasma\DriverInterface
318
     */
319 14
    protected function getOptimalConnection(): \Plasma\DriverInterface {
320 14
        if(\count($this->connections) === 0) {
321 1
            $connection = $this->createNewConnection();
322 1
            $this->busyConnections->add($connection);
323
            
324 1
            return $connection;
325
        }
326
        
327
        /** @var \Plasma\DriverInterface  $connection */
328 13
        $this->connections->rewind();
329 13
        $connection = $this->connections->current();
330
        
331 13
        $backlog = $connection->getBacklogLength();
332 13
        $state = $connection->getBusyState();
333
        
334
        /** @var \Plasma\DriverInterface  $conn */
335 13
        foreach($this->connections as $conn) {
336 13
            $cbacklog = $conn->getBacklogLength();
337 13
            $cstate = $conn->getBusyState();
338
            
339 13
            if($cbacklog === 0 && $conn->getConnectionState() === \Plasma\DriverInterface::CONNECTION_OK && $cstate == \Plasma\DriverInterface::STATE_IDLE) {
340 13
                $this->connections->delete($conn);
341 13
                $this->busyConnections->add($conn);
342
                
343 13
                return $conn;
344
            }
345
            
346
            if($backlog > $cbacklog || $state > $cstate) {
347
                $connection = $conn;
348
                $backlog = $cbacklog;
349
                $state = $cstate;
350
            }
351
        }
352
        
353
        if($this->getConnectionCount() < $this->options['connections.max']) {
354
            $connection = $this->createNewConnection();
355
        }
356
        
357
        $this->connections->delete($connection);
358
        $this->busyConnections->add($connection);
359
        
360
        return $connection;
361
    }
362
    
363
    /**
364
     * Create a new connection.
365
     * @return \Plasma\DriverInterface
366
     */
367 28
    protected function createNewConnection(): \Plasma\DriverInterface {
368 28
        $connection = $this->factory->createDriver();
369
        
370
        // We relay a driver's specific events forward, e.g. PostgreSQL notifications
371
        $connection->on('eventRelay', function (string $eventName, ...$args) use (&$connection) {
372 1
            $args[] = $connection;
373 1
            $this->emit($eventName, $args);
374 28
        });
375
        
376
        $connection->on('close', function () use (&$connection) {
377 2
            $this->connections->delete($connection);
378 2
            $this->busyConnections->delete($connection);
379
            
380 2
            $this->emit('close', array($connection));
381 28
        });
382
        
383
        $connection->on('error', function (\Throwable $error) use (&$connection) {
384 1
            $this->emit('error', array($error, $connection));
385 28
        });
386
        
387
        $connection->connect($this->uri)->then(function () use (&$connection) {
388 28
            $this->connections->add($connection);
389 28
            $this->busyConnections->delete($connection);
390
        }, function (\Throwable $error) use (&$connection) {
391
            $this->connections->delete($connection);
392
            $this->busyConnections->delete($connection);
393
            
394
            $this->emit('error', array($error, $connection));
395 28
        });
396
        
397 28
        return $connection;
398
    }
399
    
400
    /**
401
     * Validates the given options.
402
     * @param array  $options
403
     * @return void
404
     * @throws \InvalidArgumentException
405
     */
406 52
    protected function validateOptions(array $options) {
407 52
        $validator = \CharlotteDunois\Validation\Validator::make($options, array(
408 52
            'connections.max' => 'integer|min:1',
409
            'connections.lazy' => 'boolean'
410
        ));
411
        
412 52
        $validator->throw(\InvalidArgumentException::class);
413 52
    }
414
}
415