Database::getLastId()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * sysPass
4
 *
5
 * @author    nuxsmin
6
 * @link      https://syspass.org
7
 * @copyright 2012-2019, Rubén Domínguez nuxsmin@$syspass.org
8
 *
9
 * This file is part of sysPass.
10
 *
11
 * sysPass is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU General Public License as published by
13
 * the Free Software Foundation, either version 3 of the License, or
14
 * (at your option) any later version.
15
 *
16
 * sysPass is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 *  along with sysPass.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
namespace SP\Storage\Database;
26
27
use Exception;
28
use PDO;
29
use PDOStatement;
30
use SP\Core\Events\Event;
31
use SP\Core\Events\EventDispatcher;
32
use SP\Core\Events\EventMessage;
33
use SP\Core\Exceptions\ConstraintException;
34
use SP\Core\Exceptions\QueryException;
35
use SP\Core\Exceptions\SPException;
36
37
/**
38
 * Class Database
39
 *
40
 * @package SP\Storage
41
 */
42
final class Database implements DatabaseInterface
43
{
44
    /**
45
     * @var int Número de registros obtenidos
46
     */
47
    protected $numRows = 0;
48
    /**
49
     * @var int Número de campos de la consulta
50
     */
51
    protected $numFields = 0;
52
    /**
53
     * @var array Resultados de la consulta
54
     */
55
    protected $lastResult;
56
    /**
57
     * @var DBStorageInterface
58
     */
59
    protected $dbHandler;
60
    /**
61
     * @var int Último Id de elemento insertado/actualizado
62
     */
63
    private $lastId;
64
    /**
65
     * @var EventDispatcher
66
     */
67
    private $eventDispatcher;
68
69
    /**
70
     * DB constructor.
71
     *
72
     * @param DBStorageInterface $dbHandler
73
     * @param EventDispatcher    $eventDispatcher
74
     */
75
    public function __construct(DBStorageInterface $dbHandler, EventDispatcher $eventDispatcher)
76
    {
77
        $this->dbHandler = $dbHandler;
78
        $this->eventDispatcher = $eventDispatcher;
79
    }
80
81
    /**
82
     * @return int
83
     */
84
    public function getNumRows()
85
    {
86
        return $this->numRows;
87
    }
88
89
    /**
90
     * @return int
91
     */
92
    public function getNumFields()
93
    {
94
        return $this->numFields;
95
    }
96
97
    /**
98
     * @return array
99
     */
100
    public function getLastResult()
101
    {
102
        return $this->lastResult;
103
    }
104
105
    /**
106
     * @return int
107
     */
108
    public function getLastId()
109
    {
110
        return $this->lastId;
111
    }
112
113
    /**
114
     * @return DBStorageInterface
115
     */
116
    public function getDbHandler()
117
    {
118
        return $this->dbHandler;
119
    }
120
121
    /**
122
     * @param QueryData $queryData
123
     * @param bool      $fullCount
124
     *
125
     * @return QueryResult
126
     * @throws ConstraintException
127
     * @throws QueryException
128
     */
129
    public function doSelect(QueryData $queryData, $fullCount = false)
130
    {
131
        if ($queryData->getQuery() === '') {
132
            throw new QueryException($queryData->getOnErrorMessage(), QueryException::ERROR, __u('Blank query'));
133
        }
134
135
        try {
136
            $queryResult = $this->doQuery($queryData);
137
138
            if ($fullCount === true) {
139
                $queryResult->setTotalNumRows($this->getFullRowCount($queryData));
140
            }
141
142
            return $queryResult;
143
        } catch (ConstraintException $e) {
144
            processException($e);
145
146
            throw $e;
147
        } catch (QueryException $e) {
148
            processException($e);
149
150
            throw $e;
151
        } catch (Exception $e) {
152
            processException($e);
153
154
            throw new QueryException(
155
                $queryData->getOnErrorMessage(),
156
                SPException::ERROR,
157
                $e->getMessage(),
158
                $e->getCode(),
159
                $e
160
            );
161
        }
162
    }
163
164
    /**
165
     * Realizar una consulta a la BBDD.
166
     *
167
     * @param $queryData   QueryData Los datos de la consulta
168
     *
169
     * @return QueryResult
170
     * @throws QueryException
171
     * @throws ConstraintException
172
     */
173
    public function doQuery(QueryData $queryData): QueryResult
174
    {
175
        $stmt = $this->prepareQueryData($queryData);
176
177
        $this->eventDispatcher->notifyEvent('database.query',
178
            new Event($this, EventMessage::factory()
179
                ->addDescription($queryData->getQuery())
180
            )
181
        );
182
183
        if (preg_match("/^(select|show)\s/i", $queryData->getQuery())) {
184
            $this->numFields = $stmt->columnCount();
185
186
            return new QueryResult($this->fetch($queryData, $stmt));
187
        }
188
189
        return (new QueryResult())
190
            ->setAffectedNumRows($stmt->rowCount())
191
            ->setLastId($this->lastId);
192
    }
193
194
    /**
195
     * Asociar los parámetros de la consulta utilizando el tipo adecuado
196
     *
197
     * @param QueryData $queryData Los datos de la consulta
198
     * @param bool      $isCount   Indica si es una consulta de contador de registros
199
     * @param array     $options
200
     *
201
     * @return PDOStatement
202
     * @throws ConstraintException
203
     * @throws QueryException
204
     */
205
    private function prepareQueryData(QueryData $queryData, $isCount = false, array $options = [])
206
    {
207
        $query = $queryData->getQuery();
208
        $params = $queryData->getParams();
209
210
        if ($isCount === true) {
211
            $query = $queryData->getQueryCount();
212
            $params = $this->getParamsForCount($queryData);
213
        }
214
215
        try {
216
            $connection = $this->dbHandler->getConnection();
217
218
            if (!empty($params)) {
219
                $stmt = $connection->prepare($query, $options);
220
221
                foreach ($params as $param => $value) {
222
                    // Si la clave es un número utilizamos marcadores de posición "?" en
223
                    // la consulta. En caso contrario marcadores de nombre
224
                    $param = is_int($param) ? $param + 1 : ':' . $param;
225
226
                    if ($param === 'blobcontent') {
227
                        $stmt->bindValue($param, $value, PDO::PARAM_LOB);
228
                    } elseif (is_int($value)) {
229
                        $stmt->bindValue($param, $value, PDO::PARAM_INT);
230
                    } else {
231
                        $stmt->bindValue($param, $value);
232
                    }
233
                }
234
235
                $stmt->execute();
236
            } else {
237
                $stmt = $connection->query($query);
238
            }
239
240
            $this->lastId = $connection->lastInsertId();
0 ignored issues
show
Documentation Bug introduced by
The property $lastId was declared of type integer, but $connection->lastInsertId() is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
241
242
            return $stmt;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $stmt also could return the type boolean which is incompatible with the documented return type PDOStatement.
Loading history...
243
        } catch (Exception $e) {
244
            processException($e);
245
246
            switch ((int)$e->getCode()) {
247
                case 23000:
248
                    throw new ConstraintException(
249
                        __u('Integrity constraint'),
250
                        ConstraintException::ERROR,
251
                        $e->getMessage(),
252
                        $e->getCode(),
253
                        $e
254
                    );
255
            }
256
257
            throw new QueryException($e->getMessage(), QueryException::CRITICAL, $e->getCode(), 0, $e);
258
        }
259
    }
260
261
    /**
262
     * Strips out the unused params from the query count
263
     *
264
     * @param QueryData $queryData
265
     *
266
     * @return array
267
     */
268
    private function getParamsForCount(QueryData $queryData)
269
    {
270
        $countSelect = substr_count($queryData->getSelect(), '?');
271
        $countFrom = substr_count($queryData->getFrom(), '?');
272
        $countWhere = substr_count($queryData->getWhere(), '?');
273
274
        return array_slice($queryData->getParams(), $countSelect, $countFrom + $countWhere);
275
    }
276
277
    /**
278
     * @param QueryData    $queryData
279
     * @param PDOStatement $stmt
280
     *
281
     * @return array
282
     */
283
    private function fetch(QueryData $queryData, PDOStatement $stmt): array
284
    {
285
        if ($queryData->isUseKeyPair()) {
286
            return $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
287
        } elseif ($queryData->getMapClassName()) {
288
            return $stmt->fetchAll(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, $queryData->getMapClassName());
289
        }
290
291
        return $stmt->fetchAll();
292
    }
293
294
    /**
295
     * Obtener el número de filas de una consulta realizada
296
     *
297
     * @param $queryData QueryData Los datos de la consulta
298
     *
299
     * @return int Número de files de la consulta
300
     * @throws SPException
301
     */
302
    public function getFullRowCount(QueryData $queryData)
303
    {
304
        if ($queryData->getQueryCount() === '') {
305
            return 0;
306
        }
307
308
        $queryRes = $this->prepareQueryData($queryData, true);
309
        $num = (int)$queryRes->fetchColumn();
310
        $queryRes->closeCursor();
311
312
        return $num;
313
    }
314
315
    /**
316
     * Don't fetch records and return prepared statement
317
     *
318
     * @param QueryData $queryData
319
     * @param array     $options
320
     * @param bool      $buffered Set buffered behavior (useful for big datasets)
321
     *
322
     * @return PDOStatement
323
     * @throws ConstraintException
324
     * @throws QueryException
325
     */
326
    public function doQueryRaw(QueryData $queryData, array $options = [], bool $buffered = null)
327
    {
328
        if ($buffered === false
329
            && $this->dbHandler instanceof MySQLHandler
330
        ) {
331
            $this->dbHandler
332
                ->getConnection()
333
                ->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
334
        }
335
336
        return $this->prepareQueryData($queryData, $options);
0 ignored issues
show
Bug introduced by
$options of type array is incompatible with the type boolean expected by parameter $isCount of SP\Storage\Database\Database::prepareQueryData(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

336
        return $this->prepareQueryData($queryData, /** @scrutinizer ignore-type */ $options);
Loading history...
337
    }
338
339
    /**
340
     * Iniciar una transacción
341
     *
342
     * @return bool
343
     */
344
    public function beginTransaction()
345
    {
346
        $conn = $this->dbHandler->getConnection();
347
348
        if (!$conn->inTransaction()) {
349
            $result = $conn->beginTransaction();
350
351
            $this->eventDispatcher->notifyEvent('database.transaction.begin',
352
                new Event($this, EventMessage::factory()->addExtra('result', $result)));
353
354
            return $result;
355
        } else {
356
            logger('beginTransaction: already in transaction');
357
358
            return true;
359
        }
360
    }
361
362
    /**
363
     * Finalizar una transacción
364
     *
365
     * @return bool
366
     */
367
    public function endTransaction()
368
    {
369
        $conn = $this->dbHandler->getConnection();
370
371
        $result = $conn->inTransaction() && $conn->commit();
372
373
        $this->eventDispatcher->notifyEvent('database.transaction.end',
374
            new Event($this, EventMessage::factory()->addExtra('result', $result)));
375
376
        return $result;
377
    }
378
379
    /**
380
     * Rollback de una transacción
381
     *
382
     * @return bool
383
     */
384
    public function rollbackTransaction()
385
    {
386
        $conn = $this->dbHandler->getConnection();
387
388
        $result = $conn->inTransaction() && $conn->rollBack();
389
390
        $this->eventDispatcher->notifyEvent('database.transaction.rollback',
391
            new Event($this, EventMessage::factory()->addExtra('result', $result)));
392
393
        return $result;
394
    }
395
396
    /**
397
     * @param $table
398
     *
399
     * @return array
400
     */
401
    public function getColumnsForTable($table): array
402
    {
403
        $conn = $this->dbHandler->getConnection()->query("SELECT * FROM $table LIMIT 0");
404
        $columns = [];
405
406
        for ($i = 0; $i < $conn->columnCount(); $i++) {
407
            $columns[] = $conn->getColumnMeta($i)['name'];
408
        }
409
410
        return $columns;
411
    }
412
}