MySQL::heartbeat()   A
last analyzed

Complexity

Conditions 3
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 1
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * MySQL connector
4
 * User: moyo
5
 * Date: 20/10/2017
6
 * Time: 5:51 PM
7
 */
8
9
namespace Carno\Database\Connectors;
10
11
use function Carno\Coroutine\await;
12
use function Carno\Coroutine\ctx;
13
use Carno\Database\Chips\ErrorsClassify;
14
use Carno\Database\Chips\ParamsBind;
15
use Carno\Database\Contracts\Executable;
16
use Carno\Database\Contracts\Transaction;
17
use Carno\Database\Exception\ConnectingException;
18
use Carno\Database\Exception\TimeoutException;
19
use Carno\Database\Exception\TransactionException;
20
use Carno\Database\Exception\UplinkException;
21
use Carno\Database\Options\Timeouts;
22
use Carno\Database\Results\Created;
23
use Carno\Database\Results\Selected;
24
use Carno\Database\Results\Updated;
25
use Carno\Pool\Managed;
26
use Carno\Pool\Poolable;
27
use Carno\Promise\Promise;
28
use Carno\Promise\Promised;
29
use Carno\Tracing\Contracts\Vars\EXT;
30
use Carno\Tracing\Contracts\Vars\TAG;
31
use Carno\Tracing\Standard\Endpoint;
32
use Carno\Tracing\Utils\SpansCreator;
33
use Swoole\MySQL as SWMySQL;
0 ignored issues
show
Bug introduced by
The type Swoole\MySQL was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
34
35
class MySQL implements Poolable, Executable, Transaction
36
{
37
    use Managed, ParamsBind, ErrorsClassify, SpansCreator;
38
39
    /**
40
     * @var Timeouts
41
     */
42
    private $timeout = null;
43
44
    /**
45
     * @var string
46
     */
47
    private $named = null;
48
49
    /**
50
     * @var string
51
     */
52
    private $host = null;
53
54
    /**
55
     * @var int
56
     */
57
    private $port = null;
58
59
    /**
60
     * @var string
61
     */
62
    private $username = null;
63
64
    /**
65
     * @var string
66
     */
67
    private $password = null;
68
69
    /**
70
     * @var string
71
     */
72
    private $database = null;
73
74
    /**
75
     * @var string
76
     */
77
    private $charset = null;
78
79
    /**
80
     * @var SWMySQL
81
     */
82
    private $link = null;
83
84
    /**
85
     * MySQL constructor.
86
     * @param string $host
87
     * @param int $port
88
     * @param string $username
89
     * @param string $password
90
     * @param string $database
91
     * @param string $charset
92
     * @param Timeouts $timeout
93
     * @param string $named
94
     */
95
    public function __construct(
96
        string $host,
97
        int $port,
98
        string $username,
99
        string $password,
100
        string $database,
101
        string $charset = 'utf8mb4',
102
        Timeouts $timeout = null,
103
        string $named = 'mysql'
104
    ) {
105
        $this->host = $host;
106
        $this->port = $port;
107
108
        $this->username = $username;
109
        $this->password = $password;
110
111
        $this->database = $database;
112
        $this->charset = $charset;
113
114
        $this->named = $named;
115
        $this->timeout = $timeout ?? new Timeouts;
116
117
        ($this->link = new SWMySQL)->on('close', function () {
118
            $this->closed()->resolve();
119
        });
120
    }
121
122
    /**
123
     * @return array
124
     */
125
    private function options() : array
126
    {
127
        return [
128
            'host' => $this->host,
129
            'port' => $this->port,
130
            'user' => $this->username,
131
            'password' => $this->password,
132
            'database' => $this->database,
133
            'charset' => $this->charset,
134
            'timeout' => round($this->timeout->connect() / 1000, 3),
135
        ];
136
    }
137
138
    /**
139
     * @return Promised
140
     */
141
    public function connect() : Promised
142
    {
143
        return new Promise(function (Promised $promise) {
144
            $this->link->connect($this->options(), static function (SWMySQL $db, bool $success) use ($promise) {
145
                $success
146
                    ? $promise->resolve()
147
                    : $promise->throw(new ConnectingException($db->connect_error, $db->connect_errno))
148
                ;
149
            });
150
        });
151
    }
152
153
    /**
154
     * @return Promised
155
     */
156
    public function heartbeat() : Promised
157
    {
158
        return new Promise(function (Promised $promised) {
159
            $this->link->query('SELECT 1', function (SWMySQL $db, $result) use ($promised) {
160
                (is_array($result) && count($result) === 1)
161
                    ? $promised->resolve()
162
                    : $promised->reject()
163
                ;
164
            });
165
        });
166
    }
167
168
    /**
169
     * @return Promised
170
     */
171
    public function close() : Promised
172
    {
173
        $this->link->close();
174
        return $this->closed();
175
    }
176
177
    /**
178
     * @param string $sql
179
     * @param array $bind
180
     * @return Promised
181
     */
182
    public function execute(string $sql, array $bind = [])
183
    {
184
        $this->traced() && $this->newSpan($ctx = clone yield ctx(), 'sql.execute', [
0 ignored issues
show
Bug Best Practice introduced by
The expression yield ctx() returns the type Generator which is incompatible with the documented return type Carno\Promise\Promised.
Loading history...
185
            TAG::SPAN_KIND => TAG::SPAN_KIND_RPC_CLIENT,
186
            TAG::DATABASE_TYPE => 'mysql',
187
            TAG::DATABASE_INSTANCE => sprintf('%s:%d', $this->host, $this->port),
188
            TAG::DATABASE_USER => $this->username,
189
            TAG::DATABASE_STATEMENT => $sql,
190
            EXT::REMOTE_ENDPOINT => new Endpoint($this->named),
191
        ]);
192
193
        if ($bind) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $bind of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
194
            $sql = $this->binding($sql, $bind);
195
        }
196
197
        $executor = function ($fn) use ($sql) {
198
            if (false === $this->link->query($sql, $fn)) {
199
                throw new UplinkException('Unknown failure');
200
            }
201
        };
202
203
        $receiver = function (SWMySQL $db, $result) use ($sql) {
204
            if (is_bool($result)) {
205
                if ($result) {
206
                    if ($db->insert_id) {
207
                        return new Created($db->insert_id);
208
                    } else {
209
                        return new Updated($db->affected_rows);
210
                    }
211
                } else {
212
                    throw $this->executingFail($sql, $db->error, $db->errno);
213
                }
214
            } else {
215
                return new Selected($result);
216
            }
217
        };
218
219
        return $this->finishSpan(
220
            await(
221
                $executor,
222
                $receiver,
223
                $this->timeout->execute(),
224
                TimeoutException::class,
225
                sprintf('SQL [->] %s', $sql)
226
            ),
227
            $ctx ?? null
228
        );
229
    }
230
231
    /**
232
     * @param string $data
233
     * @return string
234
     */
235
    public function escape(string $data) : string
236
    {
237
        return is_numeric($data) ? $data : ($data ? $this->link->escape($data) : '');
238
    }
239
240
    /**
241
     * @return Promised
242
     */
243
    public function begin()
244
    {
245
        return $this->transCMD('begin');
246
    }
247
248
    /**
249
     * @return Promised
250
     */
251
    public function commit()
252
    {
253
        return $this->transCMD('commit');
254
    }
255
256
    /**
257
     * @return Promised
258
     */
259
    public function rollback()
260
    {
261
        return $this->transCMD('rollback');
262
    }
263
264
    /**
265
     * @param string $func
266
     * @return Promised
267
     */
268
    private function transCMD(string $func)
269
    {
270
        $this->traced() && $this->newSpan($ctx = clone yield ctx(), "trx.{$func}", [
0 ignored issues
show
Bug Best Practice introduced by
The expression yield ctx() returns the type Generator which is incompatible with the documented return type Carno\Promise\Promised.
Loading history...
271
            SPAN_KIND => SPAN_KIND_RPC_CLIENT,
272
            DATABASE_TYPE => 'mysql',
273
            DATABASE_INSTANCE => sprintf('%s:%d', $this->host, $this->port),
274
            DATABASE_USER => $this->username,
275
            DATABASE_STATEMENT => $func,
276
            EXT::REMOTE_ENDPOINT => new Endpoint($this->named),
277
        ]);
278
279
        $executor = function ($fn) use ($func) {
280
            if (false === $this->link->$func($fn)) {
281
                throw new UplinkException('Unknown failure');
282
            }
283
        };
284
285
        $receiver = static function (SWMySQL $db, bool $result) {
286
            if ($result) {
287
                return;
288
            } else {
289
                throw new TransactionException($db->error, $db->errno);
290
            }
291
        };
292
293
        return $this->finishSpan(
294
            await(
295
                $executor,
296
                $receiver,
297
                $this->timeout->execute(),
298
                TimeoutException::class,
299
                sprintf('TRANS [->] %s', $func)
300
            ),
301
            $ctx ?? null
302
        );
303
    }
304
}
305