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 ( 10b305...45007f )
by Charlotte
02:13
created

Client::prepare()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 10
ccs 6
cts 6
cp 1
crap 2
rs 10
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 50
    function __construct(\Plasma\DriverFactoryInterface $factory, string $uri, array $options = array()) {
68 50
        $this->validateOptions($options);
69
        
70 50
        $this->factory = $factory;
71 50
        $this->uri = $uri;
72 50
        $this->options = \array_merge($this->options, $options);
73
        
74 50
        $this->connections = new \CharlotteDunois\Collect\Set();
75 50
        $this->busyConnections = new \CharlotteDunois\Collect\Set();
76
        
77 50
        if(!$this->options['connections.lazy']) {
78 25
            $connection = $this->createNewConnection();
79 25
            if($connection->getConnectionState() !== \Plasma\DriverInterface::CONNECTION_OK) {
80
                $this->busyConnections->add($connection);
81
            }
82
        }
83 50
    }
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 50
    static function create(\Plasma\DriverFactoryInterface $factory, string $uri, array $options = array()): \Plasma\ClientInterface {
95 50
        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 9
    function quit(): void {
264 9
        if($this->goingAway) {
265 1
            return;
266
        }
267
        
268 8
        $this->goingAway = \React\Promise\resolve();
269
        
270
        /** @var \Plasma\DriverInterface  $conn */
271 8
        foreach($this->connections->all() as $conn) {
272 7
            $conn->quit();
273 7
            $this->connections->delete($conn);
274
        }
275
        
276
        /** @var \Plasma\DriverInterface  $conn */
277 8
        foreach($this->busyConnections->all() as $conn) {
278 1
            $conn->quit();
279 1
            $this->busyConnections->delete($conn);
280
        }
281 8
    }
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
     * Get the optimal connection.
300
     * @return \Plasma\DriverInterface
301
     */
302 13
    protected function getOptimalConnection(): \Plasma\DriverInterface {
303 13
        if(\count($this->connections) === 0) {
304 1
            $connection = $this->createNewConnection();
305 1
            $this->busyConnections->add($connection);
306
            
307 1
            return $connection;
308
        }
309
        
310
        /** @var \Plasma\DriverInterface  $connection */
311 12
        $this->connections->rewind();
312 12
        $connection = $this->connections->current();
313
        
314 12
        $backlog = $connection->getBacklogLength();
315 12
        $state = $connection->getBusyState();
316
        
317
        /** @var \Plasma\DriverInterface  $conn */
318 12
        foreach($this->connections as $conn) {
319 12
            $cbacklog = $conn->getBacklogLength();
320 12
            $cstate = $conn->getBusyState();
321
            
322 12
            if($cbacklog === 0 && $conn->getConnectionState() === \Plasma\DriverInterface::CONNECTION_OK && $cstate == \Plasma\DriverInterface::STATE_IDLE) {
323 12
                $this->connections->delete($conn);
324 12
                $this->busyConnections->add($conn);
325
                
326 12
                return $conn;
327
            }
328
            
329
            if($backlog > $cbacklog || $state > $cstate) {
330
                $connection = $conn;
331
                $backlog = $cbacklog;
332
                $state = $cstate;
333
            }
334
        }
335
        
336
        if($this->getConnectionCount() < $this->options['connections.max']) {
337
            $connection = $this->createNewConnection();
338
        }
339
        
340
        $this->connections->delete($connection);
341
        $this->busyConnections->add($connection);
342
        
343
        return $connection;
344
    }
345
    
346
    /**
347
     * Create a new connection.
348
     * @return \Plasma\DriverInterface
349
     */
350 26
    protected function createNewConnection(): \Plasma\DriverInterface {
351 26
        $connection = $this->factory->createDriver();
352
        
353
        // We relay a driver's specific events forward, e.g. PostgreSQL notifications
354
        $connection->on('eventRelay', function (string $eventName, ...$args) use (&$connection) {
355 1
            $args[] = $connection;
356 1
            $this->emit($eventName, $args);
357 26
        });
358
        
359
        $connection->on('close', function () use (&$connection) {
360 2
            $this->connections->delete($connection);
361 2
            $this->busyConnections->delete($connection);
362
            
363 2
            $this->emit('close', array($connection));
364 26
        });
365
        
366
        $connection->on('error', function (\Throwable $error) use (&$connection) {
367 1
            $this->emit('error', array($error, $connection));
368 26
        });
369
        
370
        $connection->connect($this->uri)->then(function () use (&$connection) {
371 26
            $this->connections->add($connection);
372 26
            $this->busyConnections->delete($connection);
373
        }, function (\Throwable $error) use (&$connection) {
374
            $this->connections->delete($connection);
375
            $this->busyConnections->delete($connection);
376
            
377
            $this->emit('error', array($error, $connection));
378 26
        });
379
        
380 26
        return $connection;
381
    }
382
    
383
    /**
384
     * Validates the given options.
385
     * @param array  $options
386
     * @return void
387
     * @throws \InvalidArgumentException
388
     */
389 50
    protected function validateOptions(array $options) {
390 50
        $validator = \CharlotteDunois\Validation\Validator::make($options, array(
391 50
            'connections.max' => 'integer|min:1',
392
            'connections.lazy' => 'boolean'
393
        ));
394
        
395 50
        $validator->throw(\InvalidArgumentException::class);
396 50
    }
397
}
398