Completed
Push — master ( 34e8c2...287087 )
by Mikael
01:39
created

Database::execute()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5.6264

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 21
ccs 8
cts 15
cp 0.5333
rs 9.0534
c 1
b 0
f 0
cc 4
eloc 12
nc 8
nop 2
crap 5.6264
1
<?php
2
3
namespace Anax\Database;
4
5
use \Anax\Database\Exception\Exception;
6
7
/**
8
 * Database wrapper, provides a database API on top of PHP PDO for
9
 * enhancing the API and dealing with error reporting and tracking.
10
 */
11
class Database
12
{
13
    /**
14
     * @var array        $options used when creating the PDO object
15
     * @var PDO          $pdo     the PDO object
16
     * @var PDOStatement $stmt    the latest PDOStatement used
17
     */
18
    protected $options;
19
    private $pdo = null;
20
    private $stmt = null;
21
22
23
24
    /**
25
     * Constructor creating a PDO object connecting to a choosen database.
26
     *
27
     * @param array $options containing details for connecting to the database.
28
     */
29 8
    public function __construct($options = [])
30
    {
31 8
        $this->setOptions($options);
32 8
    }
33
34
35
36
    /**
37
     * Set options and connection details.
38
     *
39
     * @param array $options containing details for connecting to the database.
40
     *
41
     * @return void
42
     */
43 9
    public function setOptions($options = [])
44
    {
45
        $default = [
46 9
            'dsn'             => null,
47 9
            'username'        => null,
48 9
            'password'        => null,
49 9
            'driver_options'  => null,
50 9
            'table_prefix'    => null,
51 9
            'fetch_mode'      => \PDO::FETCH_OBJ,
52 9
            'session_key'     => 'Anax\Database',
53 9
            'verbose'         => null,
54 9
            'debug_connect'   => false,
55 9
        ];
56 9
        $this->options = array_merge($default, $options);
57 9
    }
58
59
60
61
    /**
62
     * Set a single option.
63
     *
64
     * @param string $option which to set.
65
     * @param mixed  $value  to set.
66
     *
67
     * @return self
68
     */
69
    public function setOption($option, $value)
70
    {
71
        $this->options[$option] = $value;
72
        return $this;
73
    }
74
75
76
77
    /**
78
     * Connect to the database, allow being called multiple times
79
     * but ignore when connection is already made.
80
     *
81
     * @return self
82
     *
83
     * @throws \Anax\Database\Exception
84
     */
85 11
    public function connect()
86
    {
87 11
        if ($this->pdo) {
88 4
            return $this;
89
        }
90
91 8
        if (!isset($this->options['dsn'])) {
92 1
            throw new Exception("You can not connect, missing dsn.");
93
        }
94
95
        try {
96 8
            $this->pdo = new \PDO(
97 8
                $this->options['dsn'],
98 8
                $this->options['username'],
99 8
                $this->options['password'],
100 8
                $this->options['driver_options']
101 8
            );
102
103 8
            $this->pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, $this->options['fetch_mode']);
104 8
            $this->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
105 8
        } catch (\PDOException $e) {
106 1
            if ($this->options['debug_connect']) {
107 1
                throw $e;
108
            }
109
            throw new Exception("Could not connect to database, hiding connection details.");
110
        }
111
112 8
        return $this;
113
    }
114
115
116
117
    /**
118
     * Support arrays in params, extract array items and add to $params
119
     * and insert ? for each entry in the array.
120
     *
121
     * @param string $query  as the query to prepare.
122
     * @param array  $params the parameters that may contain arrays.
123
     *
124
     * @return array with query and params.
125
     */
126 9
    private function expandParamArray($query, $params)
127
    {
128 9
        $param = [];
129 9
        $offset = -1;
130
131 9
        foreach ($params as $val) {
132 5
            $offset = strpos($query, '?', $offset + 1);
133
134 5
            if (is_array($val)) {
135
                $nrOfItems = count($val);
136
137
                if ($nrOfItems) {
138
                    $query = substr($query, 0, $offset)
139
                        . str_repeat('?,', $nrOfItems  - 1)
140
                        . '?'
141
                        . substr($query, $offset + 1);
142
                    $param = array_merge($param, $val);
143
                } else {
144
                    $param[] = null;
145
                }
146
            } else {
147 5
                $param[] = $val;
148
            }
149 9
        }
150
151 9
        return [$query, $param];
152
    }
153
154
155
156
    /**
157
     * Execute a select-query with arguments and return the resultset.
158
     *
159
     * @param string $query  the SQL statement
160
     * @param array  $params the params array
161
     *
162
     * @return array with resultset
163
     */
164
    public function executeFetchAll($query, $params = [])
165
    {
166
        $this->execute($query, $params);
167
        return $this->stmt->fetchAll();
168
    }
169
170
171
172
    /**
173
     * Execute a select-query with arguments and return the first row
174
     * in the resultset.
175
     *
176
     * @param string $query  the SQL statement
177
     * @param array  $params the params array
178
     *
179
     * @return array with resultset
180
     */
181 1
    public function executeFetch($query, $params = [])
182
    {
183 1
        $this->execute($query, $params);
184 1
        return $this->stmt->fetch();
185
    }
186
187
188
189
    /**
190
     * Execute a select-query with arguments and insert the results into
191
     * a new object of the class.
192
     *
193
     * @param string $query  the SQL statement
194
     * @param array  $params the params array
195
     * @param string $class  the class to create an object of and insert into
196
     *
197
     * @return array with resultset
198
     */
199
    public function executeFetchClass($query, $params, $class)
200
    {
201
        $this->execute($query, $params);
202
        $this->stmt->setFetchMode(\PDO::FETCH_CLASS, $class);
203
        return $this->stmt->fetch();
204
    }
205
206
207
208
    /**
209
     * Execute a select-query with arguments and insert the results into
210
     * an existing object.
211
     *
212
     * @param string $query  the SQL statement
213
     * @param array  $params the params array
0 ignored issues
show
Documentation introduced by
There is no parameter named $params. Did you maybe mean $param?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
214
     * @param string $object the existing object to insert into
215
     *
216
     * @return array with resultset
217
     */
218
    public function executeFetchInto($query, $param, $object = null)
219
    {
220
        if (!$object) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $object of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
221
            $object = $param;
222
            $param = [];
223
        }
224
        $this->execute($query, $param);
225
        $this->stmt->setFetchMode(\PDO::FETCH_INTO, $object);
226
        return $this->stmt->fetch();
227
    }
228
229
230
231
    /**
232
     * Fetch all resultset.
233
     *
234
     * @return array with resultset.
235
     */
236
    public function fetchAll()
237
    {
238
        return $this->stmt->fetchAll();
239
    }
240
241
242
243
    /**
244
     * Fetch one resultset.
245
     *
246
     * @return array with resultset.
247
     */
248
    public function fetch()
249
    {
250
        return $this->stmt->fetch();
251
    }
252
253
254
255
    /**
256
     * Fetch one resultset as a new object from this class.
257
     *
258
     * @param object $class which type of object to instantiate.
259
     *
260
     * @return array with resultset.
261
     */
262
    public function fetchClass($class)
263
    {
264
        $this->stmt->setFetchMode(\PDO::FETCH_CLASS, $class);
265
        return $this->stmt->fetch();
266
    }
267
268
269
270
    /**
271
     * Fetch full resultset as new objects from this class.
272
     *
273
     * @param object $class which type of object to instantiate.
274
     *
275
     * @return array with resultset.
276
     */
277 2
    public function fetchAllClass($class)
278
    {
279 2
        $this->stmt->setFetchMode(\PDO::FETCH_CLASS, $class);
280 2
        return $this->stmt->fetchAll();
281
    }
282
283
284
285
    /**
286
     * Fetch one resultset into an object.
287
     *
288
     * @param object $object to insert values into.
289
     *
290
     * @return array with resultset.
291
     */
292 1
    public function fetchInto($object)
293
    {
294 1
        $this->stmt->setFetchMode(\PDO::FETCH_INTO, $object);
295 1
        return $this->stmt->fetch();
296
    }
297
298
299
300
    /**
301
     * Execute a SQL-query and ignore the resultset.
302
     *
303
     * @param string $query  the SQL statement
304
     * @param array  $params the params array
305
     *
306
     * @return self
307
     *
308
     * @throws Exception when failing to prepare question.
309
     */
310 9
    public function execute($query, $params = [])
311
    {
312 9
        list($query, $params) = $this->expandParamArray($query, $params);
313
314 9
        $this->stmt = $this->pdo->prepare($query);
315 9
        if (!$this->stmt) {
316
            $this->pdoException($query, $params);
317
        }
318
319 9
        if ($this->options["verbose"]) {
320
            echo $query . "\n";
321
            print_r($params);
322
        }
323
324 9
        $res = $this->stmt->execute($params);
325 9
        if (!$res) {
326
            $this->statementException($query, $params);
327
        }
328
329 9
        return $this;
330
    }
331
332
333
334
    /**
335
     * Throw exception using detailed message.
336
     *
337
     * @param string       $msg     detailed error message from PDO
338
     * @param string       $query   query to execute
339
     * @param array        $param   to match ? in statement
340
     *
341
     * @return void
342
     *
343
     * @throws \Anax\Database\Exception
344
     */
345
    protected function createException($msg, $query, $param)
346
    {
347
        throw new Exception(
348
            $msg
349
            . "<br><br>SQL ("
350
            . substr_count($query, "?")
351
            . " params):<br><pre>$query</pre><br>PARAMS ("
352
            . count($param)
353
            . "):<br><pre>"
354
            . implode($param, "\n")
355
            . "</pre>"
356
            . ((count(array_filter(array_keys($param), 'is_string')) > 0)
357
                ? "WARNING your params array has keys, should only have values."
358
                : null)
359
        );
360
    }
361
362
363
364
    /**
365
     * Throw exception when pdo failed using detailed message.
366
     *
367
     * @param string       $query   query to execute
368
     * @param array        $param   to match ? in statement
369
     *
370
     * @return void
371
     *
372
     * @throws \Anax\Database\Exception
373
     */
374
    protected function pdoException($query, $param)
375
    {
376
        $this->createException($this->pdo->errorInfo()[2], $query, $param);
377
    }
378
379
380
381
    /**
382
     * Throw exception when statement failed using detailed message.
383
     *
384
     * @param string       $query   query to execute
385
     * @param array        $param   to match ? in statement
386
     *
387
     * @return void
388
     *
389
     * @throws \Anax\Database\Exception
390
     */
391
    protected function statementException($query, $param)
392
    {
393
        $this->createException($this->stmt->errorInfo()[2], $query, $param);
394
    }
395
396
397
398
    /**
399
     * Return last insert id from autoincremented key on INSERT.
400
     *
401
     * @return integer as last insert id.
402
     */
403 4
    public function lastInsertId()
404
    {
405 4
        return $this->pdo->lastInsertId();
406
    }
407
408
409
410
    /**
411
    * Return rows affected of last INSERT, UPDATE, DELETE
412
    *
413
    * @return integer as rows affected on last statement
414
    */
415 1
    public function rowCount()
416
    {
417 1
        return $this->stmt->rowCount();
418
    }
419
420
421
422
    /**
423
     * Fetch one resultset as an object from this class.
424
     * OBSOLETE replaced by fetchClass
425
     *
426
     * @param object $class which type of object to instantiate.
427
     *
428
     * @return array with resultset.
429
     */
430
    public function fetchObject($class)
431
    {
432
        return $this->stmt->fetchObject($class);
433
    }
434
435
436
437
    /**
438
     * Fetch one resultset. OBSOLETE replace by fetch()
439
     *
440
     * @return array with resultset.
441
     */
442
    public function fetchOne()
443
    {
444
        return $this->stmt->fetch();
445
    }
446
}
447