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.
Test Failed
Push — master ( 376e80...8aa335 )
by Charlotte
02:12
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 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
        'maxConnections' => 5,
33
        'connect.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
     *     'maxConnections' => int, (the maximum amount of connections to open, defaults to 5)
58
     *     'connect.lazy' => bool, (whether the 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 48
    function __construct(\Plasma\DriverFactoryInterface $factory, string $uri, array $options = array()) {
68 48
        $this->validateOptions($options);
69
        
70 48
        $this->factory = $factory;
71 48
        $this->uri = $uri;
72 48
        $this->options = \array_merge($this->options, $options);
73
        
74 48
        $this->connections = new \CharlotteDunois\Collect\Set();
75 48
        $this->busyConnections = new \CharlotteDunois\Collect\Set();
76
        
77 48
        if(!$this->options['connect.lazy']) {
78 23
            $this->busyConnections->add($this->createNewConnection());
79
        }
80 48
    }
81
    
82
    /**
83
     * Creates a client with the specified factory and options.
84
     * @param \Plasma\DriverFactoryInterface  $factory
85
     * @param string                          $uri
86
     * @param array                           $options
87
     * @return \Plasma\ClientInterface
88
     * @throws \Throwable  The client implementation may throw any exception during this operation.
89
     * @see Client::__construct()
90
     */
91 48
    static function create(\Plasma\DriverFactoryInterface $factory, string $uri, array $options = array()): \Plasma\ClientInterface {
92 48
        return (new static($factory, $uri, $options));
93
    }
94
    
95
    /**
96
     * Get the amount of connections.
97
     * @return int
98
     */
99 3
    function getConnectionCount(): int {
100 3
        return ($this->connections->count() + $this->busyConnections->count());
101
    }
102
    
103
    /**
104
     * Checks a connection back in, if usable and not closing.
105
     * @param \Plasma\DriverInterface  $driver
106
     * @return void
107
     */
108 15
    function checkinConnection(\Plasma\DriverInterface $driver): void {
109 15
        if($driver->getConnectionState() !== \Plasma\DriverInterface::CONNECTION_UNUSABLE && !$this->goingAway) {
110 8
            $this->connections->add($driver);
111 8
            $this->busyConnections->delete($driver);
112
        }
113 15
    }
114
    
115
    /**
116
     * Begins a transaction. Resolves with a `Transaction` instance.
117
     *
118
     * Checks out a connection until the transaction gets committed or rolled back. If the transaction goes out of scope
119
     * and thus deallocated, the `Transaction` must check the connection back into the client.
120
     *
121
     * Some databases, including MySQL, automatically issue an implicit COMMIT when a database definition language (DDL)
122
     * statement such as DROP TABLE or CREATE TABLE is issued within a transaction.
123
     * The implicit COMMIT will prevent you from rolling back any other changes within the transaction boundary.
124
     * @param int  $isolation  See the `TransactionInterface` constants.
125
     * @return \React\Promise\PromiseInterface
126
     * @throws \Plasma\Exception
127
     * @see \Plasma\Transaction
128
     */
129 3
    function beginTransaction(int $isolation = \Plasma\TransactionInterface::ISOLATION_COMMITTED): \React\Promise\PromiseInterface {
130 3
        if($this->goingAway) {
131 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
132
        }
133
        
134 2
        $connection = $this->getOptimalConnection();
135
        
136
        return $connection->beginTransaction($this, $isolation)->then(null, function (\Throwable $error) use (&$connection) {
137 1
            $this->checkinConnection($connection);
138 1
            throw $error;
139 2
        });
140
    }
141
    
142
    /**
143
     * Executes a plain query. Resolves with a `QueryResult` instance.
144
     * @param string  $query
145
     * @return \React\Promise\PromiseInterface
146
     * @see \Plasma\QueryResultInterface
147
     */
148 5
    function query(string $query): \React\Promise\PromiseInterface {
149 5
        if($this->goingAway) {
150 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
151
        }
152
        
153 4
        $connection = $this->getOptimalConnection();
154
        
155
        return $connection->query($this, $query)->then(null, function (\Throwable $error) use (&$connection) {
156 1
            $this->checkinConnection($connection);
157 1
            throw $error;
158 4
        });
159
    }
160
    
161
    /**
162
     * Prepares a query. Resolves with a `StatementInterface` instance.
163
     * @param string  $query
164
     * @return \React\Promise\PromiseInterface
165
     * @see \Plasma\StatementInterface
166
     */
167 3
    function prepare(string $query): \React\Promise\PromiseInterface {
168 3
        if($this->goingAway) {
169 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
170
        }
171
        
172 2
        $connection = $this->getOptimalConnection();
173
        
174
        return $connection->prepare($this, $query)->then(null, function (\Throwable $error) use (&$connection) {
175 1
            $this->checkinConnection($connection);
176 1
            throw $error;
177 2
        });
178
    }
179
    
180
    /**
181
     * Prepares and executes a query. Resolves with a `QueryResultInterface` instance.
182
     * This is equivalent to prepare -> execute -> close.
183
     * If you need to execute a query multiple times, prepare the query manually for performance reasons.
184
     * @param string  $query
185
     * @param array   $params
186
     * @return \React\Promise\PromiseInterface
187
     * @throws \Plasma\Exception
188
     * @see \Plasma\StatementInterface
189
     */
190 3
    function execute(string $query, array $params = array()): \React\Promise\PromiseInterface {
191 3
        if($this->goingAway) {
192 1
            return \React\Promise\reject((new \Plasma\Exception('Client is closing all connections')));
193
        }
194
        
195 2
        $connection = $this->getOptimalConnection();
196
        
197
        return $connection->execute($this, $query, $params)->then(function ($value) use (&$connection) {
198 1
            $this->checkinConnection($connection);
199 1
            return $value;
200
        }, function (\Throwable $error) use (&$connection) {
201 1
            $this->checkinConnection($connection);
202 1
            throw $error;
203 2
        });
204
    }
205
    
206
    /**
207
     * Quotes the string for use in the query.
208
     * @param string  $str
209
     * @return string
210
     * @throws \LogicException    Thrown if the driver does not support quoting.
211
     * @throws \Plasma\Exception  Thrown if the client is closing all connections.
212
     */
213 2
    function quote(string $str): string {
214 2
        if($this->goingAway) {
215 1
            throw new \Plasma\Exception('Client is closing all connections');
216
        }
217
        
218 1
        $connection = $this->getOptimalConnection();
219 1
        $quoted = $connection->quote($str);
220
        
221 1
        $this->checkinConnection($connection);
222 1
        return $quoted;
223
    }
224
    
225
    /**
226
     * Closes all connections gracefully after processing all outstanding requests.
227
     * @return \React\Promise\PromiseInterface
228
     */
229 1
    function close(): \React\Promise\PromiseInterface {
230 1
        if($this->goingAway) {
231
            return $this->goingAway;
232
        }
233
        
234 1
        $deferred = new \React\Promise\Deferred();
235 1
        $this->goingAway = $deferred->promise();
236
        
237 1
        $closes = array();
238
        
239
        /** @var \Plasma\DriverInterface  $conn */
240 1
        foreach($this->connections as $conn) {
241
            $closes[] = $conn->close();
242
            $this->connections->delete($conn);
243
        }
244
        
245
        /** @var \Plasma\DriverInterface  $conn */
246 1
        foreach($this->busyConnections as $conn) {
247 1
            $closes[] = $conn->close();
248 1
            $this->busyConnections->delete($conn);
249
        }
250
        
251 1
        \React\Promise\all($closes)->then(array($deferred, 'resolve'), array($deferred, 'reject'));
252 1
        return $this->goingAway;
253
    }
254
    
255
    /**
256
     * Forcefully closes the connection, without waiting for any outstanding requests. This will reject all oustanding requests.
257
     * @return void
258
     */
259 7
    function quit(): void {
260 7
        if($this->goingAway) {
261
            return;
262
        }
263
        
264 7
        $this->goingAway = \React\Promise\resolve();
265
        
266
        /** @var \Plasma\DriverInterface  $conn */
267 7
        foreach($this->connections as $conn) {
268 6
            $conn->quit();
269 6
            $this->connections->delete($conn);
270
        }
271
        
272
        /** @var \Plasma\DriverInterface  $conn */
273 7
        foreach($this->busyConnections as $conn) {
274 7
            $conn->quit();
275 7
            $this->busyConnections->delete($conn);
276
        }
277 7
    }
278
    
279
    /**
280
     * Runs the given command.
281
     * @param \Plasma\CommandInterface  $command
282
     * @return mixed  Return depends on command and driver.
283
     * @throws \Plasma\Exception  Thrown if the client is closing all connections.
284
     */
285 3
    function runCommand(\Plasma\CommandInterface $command) {
286 3
        if($this->goingAway) {
287 1
            throw new \Plasma\Exception('Client is closing all connections');
288
        }
289
        
290 2
        $connection = $this->getOptimalConnection();
291 2
        return $connection->runCommand($this, $command);
292
    }
293
    
294
    /**
295
     * Get the optimal connection.
296
     * @return \Plasma\DriverInterface
297
     */
298 13
    protected function getOptimalConnection(): \Plasma\DriverInterface {
299 13
        if(\count($this->connections) === 0) {
300 1
            return $this->createNewConnection();
301
        }
302
        
303
        /** @var \Plasma\DriverInterface  $connection */
304 12
        $this->connections->rewind();
305 12
        $connection = $this->connections->current();
306
        
307 12
        $backlog = $connection->getBacklogLength();
308 12
        $state = $connection->getBusyState();
309
        
310
        /** @var \Plasma\DriverInterface  $conn */
311 12
        foreach($this->connections as $conn) {
312 12
            $cbacklog = $conn->getBacklogLength();
313 12
            $cstate = $conn->getBusyState();
314
            
315 12
            if($cbacklog === 0 && $conn->getConnectionState() === \Plasma\DriverInterface::CONNECTION_OK && $cstate == \Plasma\DriverInterface::STATE_IDLE) {
316 12
                $this->connections->delete($conn);
317 12
                $this->busyConnections->add($connection);
318
                
319 12
                return $conn;
320
            }
321
            
322
            if($backlog > $cbacklog || $state > $cstate) {
323
                $connection = $conn;
324
                $backlog = $cbacklog;
325
                $state = $cstate;
326
            }
327
        }
328
        
329
        if($this->getConnectionCount() < $this->options['maxConnections']) {
330
            return $this->createNewConnection();
331
        }
332
        
333
        $this->connections->delete($connection);
334
        $this->busyConnections->add($connection);
335
        
336
        return $connection;
337
    }
338
    
339
    /**
340
     * Create a new connection.
341
     * @return \Plasma\DriverInterface
342
     */
343 24
    protected function createNewConnection(): \Plasma\DriverInterface {
344 24
        $connection = $this->factory->createDriver();
345
        
346
        // We relay a driver's specific events forward, e.g. PostgreSQL notifications
347
        $connection->on('eventRelay', function (string $eventName, ...$args) use (&$connection) {
348 1
            $args[] = $connection;
349 1
            $this->emit($eventName, $args);
350 24
        });
351
        
352
        $connection->on('close', function () use (&$connection) {
353 2
            $this->connections->delete($connection);
354 2
            $this->busyConnections->delete($connection);
355
            
356 2
            $this->emit('close', array($connection));
357 24
        });
358
        
359
        $connection->on('error', function (\Throwable $error) use (&$connection) {
360 1
            $this->emit('error', array($error, $connection));
361 24
        });
362
        
363
        $connection->connect($this->uri)->then(function () use (&$connection) {
364 24
            $this->connections->add($connection);
365 24
            $this->busyConnections->delete($connection);
366
        }, function (\Throwable $error) use (&$connection) {
367
            $this->connections->delete($connection);
368
            $this->busyConnections->delete($connection);
369
            
370
            $this->emit('error', array($error, $connection));
371 24
        });
372
        
373 24
        return $connection;
374
    }
375
    
376
    /**
377
     * Validates the given options.
378
     * @param array  $options
379
     * @return void
380
     * @throws \InvalidArgumentException
381
     */
382 48
    protected function validateOptions(array $options) {
383 48
        $validator = \CharlotteDunois\Validation\Validator::make($options, array(
384 48
            'maxConnections' => 'integer|min:1',
385
            'connect.lazy' => 'boolean'
386
        ));
387
        
388 48
        $validator->throw(\InvalidArgumentException::class);
389 48
    }
390
}
391