PDO::exec()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
1
<?php
2
3
/**
4
 * Copyright: Swlib
5
 * Author: Twosee <[email protected]>
6
 * Modifier: Albert Chen
7
 * License: Apache 2.0
8
 */
9
10
namespace SwooleTW\Http\Coroutine;
11
12
use Exception;
13
use Illuminate\Database\QueryException;
14
use Illuminate\Support\Arr;
15
use PDO as BasePDO;
16
17
/**
18
 * Class PDO
19
 */
20
class PDO extends BasePDO
21
{
22
    public static $keyMap = [
23
        'dbname' => 'database',
24
    ];
25
26
    private static $options = [
27
        'host' => '',
28
        'port' => 3306,
29
        'user' => '',
30
        'password' => '',
31
        'database' => '',
32
        'charset' => 'utf8mb4',
33
        'strict_type' => true,
34
        'timeout' => -1,
35
    ];
36
37
    /** @var \Swoole\Coroutine\Mysql */
38
    public $client;
39
40
    public $inTransaction = false;
41
42
    /**
43
     * PDO constructor.
44
     *
45
     * @param string $dsn
46
     * @param string $username
47
     * @param string $password
48
     * @param array $options
49
     *
50
     * @throws \SwooleTW\Http\Coroutine\ConnectionException
51
     */
52
    public function __construct(string $dsn, string $username = '', string $password = '', array $options = [])
53
    {
54
        parent::__construct($dsn, $username, $password, $options);
55
        $this->setClient();
56
        $this->connect($this->getOptions(...func_get_args()));
57
    }
58
59
    /**
60
     * @param mixed $client
61
     */
62
    protected function setClient($client = null)
63
    {
64
        $this->client = $client ?: new \Swoole\Coroutine\Mysql;
65
    }
66
67
    /**
68
     * @param array $options
69
     *
70
     * @return $this
71
     * @throws \SwooleTW\Http\Coroutine\ConnectionException
72
     */
73
    protected function connect(array $options = [])
74
    {
75
        $this->client->connect($options);
76
77
        if (! $this->client->connected) {
78
            $message = $this->client->connect_error ?: $this->client->error;
79
            $errorCode = $this->client->connect_errno ?: $this->client->errno;
80
81
            throw new ConnectionException($message, $errorCode);
82
        }
83
84
        return $this;
85
    }
86
87
    /**
88
     * @param $dsn
89
     * @param $username
90
     * @param $password
91
     * @param $driverOptions
92
     *
93
     * @return array
94
     */
95
    protected function getOptions($dsn, $username, $password, $driverOptions)
96
    {
97
        $dsn = explode(':', $dsn);
98
        $driver = ucwords(array_shift($dsn));
99
        $dsn = explode(';', implode(':', $dsn));
100
        $configuredOptions = [];
101
102
        static::checkDriver($driver);
103
104
        foreach ($dsn as $kv) {
105
            $kv = explode('=', $kv);
106
            if (count($kv)) {
107
                $configuredOptions[$kv[0]] = $kv[1] ?? '';
108
            }
109
        }
110
111
        $authorization = [
112
            'user' => $username,
113
            'password' => $password,
114
        ];
115
116
        $configuredOptions = $driverOptions + $authorization + $configuredOptions;
117
118
        foreach (static::$keyMap as $pdoKey => $swpdoKey) {
119
            if (isset($configuredOptions[$pdoKey])) {
120
                $configuredOptions[$swpdoKey] = $configuredOptions[$pdoKey];
121
                unset($configuredOptions[$pdoKey]);
122
            }
123
        }
124
125
        return array_merge(static::$options, $configuredOptions);
0 ignored issues
show
Bug introduced by
Since $options is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $options to at least protected.
Loading history...
126
    }
127
128
    /**
129
     * @param string $driver
130
     */
131
    public static function checkDriver(string $driver)
132
    {
133
        if (! in_array($driver, static::getAvailableDrivers())) {
134
            throw new \InvalidArgumentException("{$driver} driver is not supported yet.");
135
        }
136
    }
137
138
    /**
139
     * @return array
140
     */
141
    public static function getAvailableDrivers()
142
    {
143
        return ['Mysql'];
144
    }
145
146
    /**
147
     * @return bool|void
148
     */
149
    public function beginTransaction()
150
    {
151
        $this->client->begin();
152
        $this->inTransaction = true;
153
    }
154
155
    /**
156
     * @return bool|void
157
     */
158
    public function rollBack()
159
    {
160
        $this->client->rollback();
161
        $this->inTransaction = false;
162
    }
163
164
    /**
165
     * @return bool|void
166
     */
167
    public function commit()
168
    {
169
        $this->client->commit();
170
        $this->inTransaction = true;
171
    }
172
173
    /**
174
     * @return bool
175
     */
176
    public function inTransaction()
177
    {
178
        return $this->inTransaction;
179
    }
180
181
    /**
182
     * @param null $seqname
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $seqname is correct as it would always require null to be passed?
Loading history...
183
     *
184
     * @return int|string
185
     */
186
    public function lastInsertId($seqname = null)
187
    {
188
        return $this->client->insert_id;
189
    }
190
191
    /**
192
     * @return mixed|void
193
     */
194
    public function errorCode()
195
    {
196
        $this->client->errno;
197
    }
198
199
    /**
200
     * @return array
201
     */
202
    public function errorInfo()
203
    {
204
        return [
205
            $this->client->errno,
206
            $this->client->errno,
207
            $this->client->error,
208
        ];
209
    }
210
211
    /**
212
     * @param string $statement
213
     *
214
     * @return int
215
     */
216
    public function exec($statement): int
217
    {
218
        $this->query($statement);
219
220
        return $this->client->affected_rows;
221
    }
222
223
    /**
224
     * @param string $statement
225
     * @param int $mode
226
     * @param mixed $arg3
227
     * @param array $ctorargs
228
     *
229
     * @return array|bool|false|\PDOStatement
230
     */
231
    public function query($statement, $mode = PDO::ATTR_DEFAULT_FETCH_MODE, $arg3 = null, array $ctorargs = [])
232
    {
233
        $result = $this->client->query($statement, Arr::get(self::$options, 'timeout'));
234
235
        if ($result === false) {
236
            $exception = new Exception($this->client->error, $this->client->errno);
237
            throw new QueryException($statement, [], $exception);
238
        }
239
240
        return $result;
241
    }
242
243
    /**
244
     * @param string $statement
245
     * @param array $options
246
     *
247
     * @return bool|\PDOStatement|\SwooleTW\Http\Coroutine\PDOStatement
248
     */
249
    public function prepare($statement, $options = null)
250
    {
251
        $options = is_null($options) ? [] : $options;
252
        if (strpos($statement, ':') !== false) {
253
            $i = 0;
254
            $bindKeyMap = [];
255
            $statement = preg_replace_callback('/:([a-zA-Z_]\w*?)\b/', function ($matches) use (&$i, &$bindKeyMap) {
256
                $bindKeyMap[$matches[1]] = $i++;
257
258
                return '?';
259
            }, $statement);
260
        }
261
262
        $stmtObj = $this->client->prepare($statement);
263
264
        if ($stmtObj) {
0 ignored issues
show
introduced by
$stmtObj is of type Swoole\Coroutine\Mysql\Statement, thus it always evaluated to true.
Loading history...
265
            $stmtObj->bindKeyMap = $bindKeyMap ?? [];
0 ignored issues
show
Bug introduced by
The property bindKeyMap does not seem to exist on Swoole\Coroutine\Mysql\Statement.
Loading history...
266
267
            return new PDOStatement($this, $stmtObj, $options);
268
        } else {
269
            $statementException = new StatementException($this->client->error, $this->client->errno);
270
            throw new QueryException($statement, [], $statementException);
271
        }
272
    }
273
274
    /**
275
     * @param int $attribute
276
     *
277
     * @return bool|mixed|string
278
     */
279
    public function getAttribute($attribute)
280
    {
281
        switch ($attribute) {
282
            case \PDO::ATTR_AUTOCOMMIT:
283
                return true;
284
            case \PDO::ATTR_CASE:
285
            case \PDO::ATTR_CLIENT_VERSION:
286
            case \PDO::ATTR_CONNECTION_STATUS:
287
                return $this->client->connected;
288
            case \PDO::ATTR_DRIVER_NAME:
289
            case \PDO::ATTR_ERRMODE:
290
                return 'Swoole Style';
291
            case \PDO::ATTR_ORACLE_NULLS:
292
            case \PDO::ATTR_PERSISTENT:
293
            case \PDO::ATTR_PREFETCH:
294
            case \PDO::ATTR_SERVER_INFO:
295
                return self::$options['timeout'];
296
            case \PDO::ATTR_SERVER_VERSION:
297
                return 'Swoole Mysql';
298
            case \PDO::ATTR_TIMEOUT:
299
            default:
300
                throw new \InvalidArgumentException('Not implemented yet!');
301
        }
302
    }
303
304
    /**
305
     * @param string $string
306
     * @param null $paramtype
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $paramtype is correct as it would always require null to be passed?
Loading history...
307
     *
308
     * @return string|void
309
     */
310
    public function quote($string, $paramtype = null)
311
    {
312
        throw new \BadMethodCallException(
313
            <<<TXT
314
If you are using this function to build SQL statements,
315
you are strongly recommended to use PDO::prepare() to prepare SQL statements
316
with bound parameters instead of using PDO::quote() to interpolate user input into an SQL statement.
317
Prepared statements with bound parameters are not only more portable, more convenient,
318
immune to SQL injection, but are often much faster to execute than interpolated queries,
319
as both the server and client side can cache a compiled form of the query.
320
TXT
321
        );
322
    }
323
324
    /**
325
     * Destructor
326
     */
327
    public function __destruct()
328
    {
329
        $this->client->close();
330
    }
331
}
332