Passed
Push — devel-3.0 ( ca620b...2581df )
by Rubén
03:02
created

Database::rollbackTransaction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 10
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-2018, 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 PDO;
28
use PDOStatement;
29
use SP\Core\Events\Event;
30
use SP\Core\Events\EventDispatcher;
31
use SP\Core\Events\EventMessage;
32
use SP\Core\Exceptions\ConstraintException;
33
use SP\Core\Exceptions\QueryException;
34
use SP\Core\Exceptions\SPException;
35
36
/**
37
 * Class Database
38
 *
39
 * @package SP\Storage
40
 */
41
final class Database implements DatabaseInterface
42
{
43
    /**
44
     * @var int Número de registros obtenidos
45
     */
46
    protected $numRows = 0;
47
    /**
48
     * @var int Número de campos de la consulta
49
     */
50
    protected $numFields = 0;
51
    /**
52
     * @var array Resultados de la consulta
53
     */
54
    protected $lastResult;
55
    /**
56
     * @var DBStorageInterface
57
     */
58
    protected $dbHandler;
59
    /**
60
     * @var int Último Id de elemento insertado/actualizado
61
     */
62
    private $lastId;
63
    /**
64
     * @var EventDispatcher
65
     */
66
    private $eventDispatcher;
67
68
    /**
69
     * DB constructor.
70
     *
71
     * @param DBStorageInterface $dbHandler
72
     * @param EventDispatcher    $eventDispatcher
73
     */
74
    public function __construct(DBStorageInterface $dbHandler, EventDispatcher $eventDispatcher)
75
    {
76
        $this->dbHandler = $dbHandler;
77
        $this->eventDispatcher = $eventDispatcher;
78
    }
79
80
    /**
81
     * @return int
82
     */
83
    public function getNumRows()
84
    {
85
        return $this->numRows;
86
    }
87
88
    /**
89
     * @return int
90
     */
91
    public function getNumFields()
92
    {
93
        return $this->numFields;
94
    }
95
96
    /**
97
     * @return array
98
     */
99
    public function getLastResult()
100
    {
101
        return $this->lastResult;
102
    }
103
104
    /**
105
     * @return int
106
     */
107
    public function getLastId()
108
    {
109
        return $this->lastId;
110
    }
111
112
    /**
113
     * @return DBStorageInterface
114
     */
115
    public function getDbHandler()
116
    {
117
        return $this->dbHandler;
118
    }
119
120
    /**
121
     * @param QueryData $queryData
122
     * @param bool      $fullCount
123
     *
124
     * @return QueryResult
125
     * @throws ConstraintException
126
     * @throws QueryException
127
     */
128
    public function doSelect(QueryData $queryData, $fullCount = false)
129
    {
130
        if ($queryData->getQuery() === '') {
131
            throw new QueryException($queryData->getOnErrorMessage(), QueryException::ERROR, __u('Consulta en blanco'));
132
        }
133
134
        try {
135
            $queryResult = $this->doQuery($queryData);
136
137
            if ($fullCount === true) {
138
                $queryResult->setTotalNumRows($this->getFullRowCount($queryData));
139
            }
140
141
            return $queryResult;
142
        } catch (ConstraintException $e) {
143
            processException($e);
144
145
            throw $e;
146
        } catch (QueryException $e) {
147
            processException($e);
148
149
            throw $e;
150
        } catch (\Exception $e) {
151
            processException($e);
152
153
            throw new QueryException(
154
                $queryData->getOnErrorMessage(),
155
                SPException::ERROR,
156
                $e->getMessage(),
157
                $e->getCode(),
158
                $e
159
            );
160
        }
161
    }
162
163
    /**
164
     * Realizar una consulta a la BBDD.
165
     *
166
     * @param $queryData   QueryData Los datos de la consulta
167
     * @param $getRawData  bool    realizar la consulta para obtener registro a registro
168
     *
169
     * @return QueryResult
170
     * @throws QueryException
171
     * @throws ConstraintException
172
     */
173
    public function doQuery(QueryData $queryData, $getRawData = false)
174
    {
175
        /** @var PDOStatement $stmt */
176
        $stmt = $this->prepareQueryData($queryData);
177
178
        $this->eventDispatcher->notifyEvent('database.query',
179
            new Event($this, EventMessage::factory()
180
                ->addDescription($queryData->getQuery())
181
            )
182
        );
183
184
        if (preg_match("/^(select|show)\s/i", $queryData->getQuery())) {
185
            $this->numFields = $stmt->columnCount();
186
187
            return new QueryResult($stmt->fetchAll());
188
        }
189
190
        return (new QueryResult())
191
            ->setAffectedNumRows($stmt->rowCount())
192
            ->setLastId($this->lastId);
193
    }
194
195
    /**
196
     * Asociar los parámetros de la consulta utilizando el tipo adecuado
197
     *
198
     * @param $queryData QueryData Los datos de la consulta
199
     * @param $isCount   bool   Indica si es una consulta de contador de registros
200
     *
201
     * @return \PDOStatement|false
202
     * @throws QueryException
203
     * @throws ConstraintException
204
     */
205
    private function prepareQueryData(QueryData $queryData, $isCount = false)
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);
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
            if ($queryData->isUseKeyPair()) {
241
                $stmt->setFetchMode(PDO::FETCH_KEY_PAIR);
242
            } elseif (null !== $queryData->getMapClass()) {
243
                $stmt->setFetchMode(PDO::FETCH_INTO, $queryData->getMapClass());
244
            } elseif ($queryData->getMapClassName()) {
245
                $stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, $queryData->getMapClassName());
0 ignored issues
show
Bug introduced by
$queryData->getMapClassName() of type string is incompatible with the type integer expected by parameter $columnNumber of PDOStatement::setFetchMode(). ( Ignorable by Annotation )

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

245
                $stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, /** @scrutinizer ignore-type */ $queryData->getMapClassName());
Loading history...
246
            } else {
247
                $stmt->setFetchMode(PDO::FETCH_OBJ);
248
            }
249
250
            $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...
251
252
            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|false.
Loading history...
253
        } catch (\Exception $e) {
254
            processException($e);
255
256
            switch ((int)$e->getCode()) {
257
                case 23000:
258
                    throw new ConstraintException(
259
                        __u('Restricción de integridad'),
260
                        ConstraintException::ERROR,
261
                        $e->getMessage(),
262
                        $e->getCode(),
263
                        $e
264
                    );
265
            }
266
267
            throw new QueryException($e->getMessage(), QueryException::CRITICAL, $e->getCode(), 0, $e);
268
        }
269
    }
270
271
    /**
272
     * Strips out the unused params from the query count
273
     *
274
     * @param QueryData $queryData
275
     *
276
     * @return array
277
     */
278
    private function getParamsForCount(QueryData $queryData)
279
    {
280
        $countSelect = substr_count($queryData->getSelect(), '?');
281
        $countFrom = substr_count($queryData->getFrom(), '?');
282
        $countWhere = substr_count($queryData->getWhere(), '?');
283
284
        return array_slice($queryData->getParams(), $countSelect, $countFrom + $countWhere);
285
    }
286
287
    /**
288
     * Obtener el número de filas de una consulta realizada
289
     *
290
     * @param $queryData QueryData Los datos de la consulta
291
     *
292
     * @return int Número de files de la consulta
293
     * @throws SPException
294
     */
295
    public function getFullRowCount(QueryData $queryData)
296
    {
297
        if ($queryData->getQueryCount() === '') {
298
            return 0;
299
        }
300
301
        $queryRes = $this->prepareQueryData($queryData, true);
302
        $num = (int)$queryRes->fetchColumn();
303
        $queryRes->closeCursor();
304
305
        return $num;
306
    }
307
308
    /**
309
     * Don't fetch records and return prepared statement
310
     *
311
     * @param QueryData $queryData
312
     *
313
     * @return \PDOStatement
314
     * @throws ConstraintException
315
     * @throws QueryException
316
     */
317
    public function doQueryRaw(QueryData $queryData)
318
    {
319
        return $this->prepareQueryData($queryData);
320
    }
321
322
    /**
323
     * Iniciar una transacción
324
     *
325
     * @return bool
326
     */
327
    public function beginTransaction()
328
    {
329
        $conn = $this->dbHandler->getConnection();
330
331
        if (!$conn->inTransaction()) {
332
            $result = $conn->beginTransaction();
333
334
            $this->eventDispatcher->notifyEvent('database.transaction.begin',
335
                new Event($this, EventMessage::factory()->addData('result', $result)));
336
337
            return $result;
338
        } else {
339
            logger('beginTransaction: already in transaction');
340
341
            return true;
342
        }
343
    }
344
345
    /**
346
     * Finalizar una transacción
347
     *
348
     * @return bool
349
     */
350
    public function endTransaction()
351
    {
352
        $conn = $this->dbHandler->getConnection();
353
354
        $result = $conn->inTransaction() && $conn->commit();
355
356
        $this->eventDispatcher->notifyEvent('database.transaction.end',
357
            new Event($this, EventMessage::factory()->addData('result', $result)));
358
359
        return $result;
360
    }
361
362
    /**
363
     * Rollback de una transacción
364
     *
365
     * @return bool
366
     */
367
    public function rollbackTransaction()
368
    {
369
        $conn = $this->dbHandler->getConnection();
370
371
        $result = $conn->inTransaction() && $conn->rollBack();
372
373
        $this->eventDispatcher->notifyEvent('database.transaction.rollback',
374
            new Event($this, EventMessage::factory()->addData('result', $result)));
375
376
        return $result;
377
    }
378
}