Completed
Push — master ( a0d072...15d2c3 )
by Mikael
02:14
created

Database::fetchOne()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
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
    public function __construct(array $options = [])
30
    {
31
        $this->setOptions($options);
32
    }
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
    public function setOptions(array $options = []) : void
44
    {
45
        $default = [
46
            "dsn"             => null,
47
            "username"        => null,
48
            "password"        => null,
49
            "driver_options"  => null,
50
            "table_prefix"    => null,
51
            "fetch_mode"      => \PDO::FETCH_OBJ,
52
            "emulate_prepares" => false,
53
            "session_key"     => "Anax\Database",
54
            "verbose"         => false,
55
            "debug_connect"   => false,
56
        ];
57
58
        $this->options = array_merge($default, $options);
59
    }
60
61
62
63
    /**
64
     * Set a single option for configuration and connection details.
65
     *
66
     * @param string $option which to set.
67
     * @param mixed  $value  to set.
68
     *
69
     * @return self
70
     */
71
    public function setOption(string $option, $value) : object
72
    {
73
        $this->options[$option] = $value;
74
        return $this;
75
    }
76
77
78
79
    /**
80
     * Connect to the database, allow being called multiple times
81
     * but ignore when connection is already made.
82
     *
83
     * @throws \Anax\Database\Exception
84
     *
85
     * @return self
86
     */
87
    public function connect() : object
88
    {
89
        if ($this->pdo) {
90
            return $this;
91
        }
92
93
        if (!isset($this->options["dsn"])) {
94
            throw new Exception("You can not connect, missing dsn.");
95
        }
96
97
        try {
98
            $this->pdo = new \PDO(
99
                $this->options["dsn"],
100
                $this->options["username"],
101
                $this->options["password"],
102
                $this->options["driver_options"]
103
            );
104
            $this->pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, $this->options['fetch_mode']);
105
            $this->pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, $this->options['emulate_prepares']);
106
        } catch (\PDOException $e) {
107
            if ($this->options["debug_connect"]) {
108
                throw $e;
109
            }
110
            throw new Exception("Could not connect to database, hiding connection details.");
111
        }
112
113
        return $this;
114
    }
115
116
117
118
    /**
119
     * Support arrays in params, extract array items and add to $params
120
     * and insert ? for each entry in the array.
121
     *
122
     * @param string $query  as the query to prepare.
123
     * @param array  $params the parameters that may contain arrays.
124
     *
125
     * @return array with query and params.
126
     */
127
    private function expandParamArray(string $query, array $params) : array
128
    {
129
        $param = [];
130
        $offset = -1;
131
132
        foreach ($params as $val) {
133
            $offset = strpos($query, "?", $offset + 1);
134
135
            if (is_array($val)) {
136
                $nrOfItems = count($val);
137
138
                if ($nrOfItems) {
139
                    $query = substr($query, 0, $offset)
140
                        . str_repeat("?,", $nrOfItems  - 1)
141
                        . "?"
142
                        . substr($query, $offset + 1);
143
                    $param = array_merge($param, $val);
144
                } else {
145
                    $param[] = null;
146
                }
147
            } else {
148
                $param[] = $val;
149
            }
150
        }
151
152
        return [$query, $param];
153
    }
154
155
156
157
    /**
158
     * Execute a select-query with arguments and return the resultset.
159
     *
160
     * @param string $query  the SQL statement
161
     * @param array  $params the params array
162
     *
163
     * @return mixed with resultset
164
     */
165
    public function executeFetchAll(string $query, array $params = [])
166
    {
167
        $this->execute($query, $params);
168
        return $this->stmt->fetchAll();
169
    }
170
171
172
173
    /**
174
     * Execute a select-query with arguments and return the first row
175
     * in the resultset.
176
     *
177
     * @param string $query  the SQL statement
178
     * @param array  $params the params array
179
     *
180
     * @return mixed with resultset
181
     */
182
    public function executeFetch(string $query, array $params = [])
183
    {
184
        $this->execute($query, $params);
185
        return $this->stmt->fetch();
186
    }
187
188
189
190
    /**
191
     * Execute a select-query with arguments and insert the results into
192
     * a new object of the class.
193
     *
194
     * @param string $query  the SQL statement
195
     * @param array  $params the params array
196
     * @param string $class  the class to create an object of and insert into
197
     *
198
     * @return object with resultset
199
     */
200
    public function executeFetchClass(
201
        string $query,
202
        array $params,
203
        string $class
204
    ) : object {
205
        $this->execute($query, $params);
206
        $this->stmt->setFetchMode(\PDO::FETCH_CLASS, $class);
207
        return $this->stmt->fetch();
208
    }
209
210
211
212
    /**
213
     * Execute a select-query with arguments and insert the results into
214
     * an existing object.
215
     *
216
     * @param string $query  the SQL statement
217
     * @param array  $params the params array
218
     * @param string $object the existing object to insert into
219
     *
220
     * @return object with resultset
221
     */
222
    public function executeFetchInto(
223
        string $query,
224
        array $params,
225
        string $object = null
226
    ) : object {
227
        if (is_null($object)) {
228
            $object = $params;
229
            $params = [];
230
        }
231
        $this->execute($query, $params);
232
        $this->stmt->setFetchMode(\PDO::FETCH_INTO, $object);
233
        return $this->stmt->fetch();
234
    }
235
236
237
238
    /**
239
     * Fetch all rows into the resultset.
240
     *
241
     * @return array with resultset.
242
     */
243
    public function fetchAll() : array
244
    {
245
        return $this->stmt->fetchAll();
246
    }
247
248
249
250
    /**
251
     * Fetch one row as the resultset.
252
     *
253
     * @return mixed with resultset.
254
     */
255
    public function fetch()
256
    {
257
        return $this->stmt->fetch();
258
    }
259
260
261
262
    /**
263
     * Fetch one resultset as a new object from this class.
264
     *
265
     * @param string $classname which type of object to instantiate.
266
     *
267
     * @return object containing resultset as properties.
268
     */
269
    public function fetchClass(string $classname) : object
270
    {
271
        $this->stmt->setFetchMode(\PDO::FETCH_CLASS, $classname);
272
        return $this->stmt->fetch();
273
    }
274
275
276
277
    /**
278
     * Fetch all rows as the resultset instantiated as new objects from
279
     * this class.
280
     *
281
     * @param string $classname which type of object to instantiate.
282
     *
283
     * @return array with resultset containing objects of $classname.
284
     */
285
    public function fetchAllClass(string $classname) : array
286
    {
287
        $this->stmt->setFetchMode(\PDO::FETCH_CLASS, $classname);
288
        return $this->stmt->fetchAll();
289
    }
290
291
292
293
    /**
294
     * Fetch one resultset into an object.
295
     *
296
     * @param object $object to insert values into.
297
     *
298
     * @return array with resultset.
299
     */
300
    public function fetchInto(object $object) : object
301
    {
302
        $this->stmt->setFetchMode(\PDO::FETCH_INTO, $object);
303
        return $this->stmt->fetch();
304
    }
305
306
307
308
    /**
309
     * Execute a SQL-query and ignore the resultset.
310
     *
311
     * @param string $query  the SQL statement
312
     * @param array  $params the params array
313
     *
314
     * @throws Exception when failing to prepare question or when not connected.
315
     *
316
     * @return self
317
     */
318
    public function execute(string $query, array $params = []) : object
319
    {
320
        list($query, $params) = $this->expandParamArray($query, $params);
321
322
        if (!$this->pdo) {
323
            $this->createException("Did you forget to connect to the database?", $query, $params);
324
        }
325
326
        $this->stmt = $this->pdo->prepare($query);
327
        if (!$this->stmt) {
328
            $this->pdoException($query, $params);
329
        }
330
331
        if ($this->options["verbose"]) {
332
            echo $query . "\n";
333
            print_r($params);
334
        }
335
336
        $res = $this->stmt->execute($params);
337
        if (!$res) {
338
            $this->statementException($query, $params);
339
        }
340
341
        return $this;
342
    }
343
344
345
346
    /**
347
     * Throw exception with detailed message.
348
     *
349
     * @param string $msg    detailed error message from PDO
350
     * @param string $query  query to execute
351
     * @param array  $params to match ? in statement
352
     *
353
     * @throws Anax\Database\Exception
354
     *
355
     * @return void
356
     */
357
    protected function createException(
358
        string $msg,
359
        string $query,
360
        array $params
361
    ) : void {
362
        throw new Exception(
363
            $msg
364
            . "<br><br>SQL ("
365
            . substr_count($query, "?")
366
            . " params):<br><pre>$query</pre><br>PARAMS ("
367
            . count($params)
368
            . "):<br><pre>"
369
            . implode($params, "\n")
370
            . "</pre>"
371
        );
372
    }
373
374
375
376
    /**
377
     * Throw exception when pdo failed using detailed message.
378
     *
379
     * @param string $query  query to execute
380
     * @param array  $params to match ? in statement
381
     *
382
     * @return void
383
     */
384
    protected function pdoException(string $query, array $params) : void
385
    {
386
        $this->createException($this->pdo->errorInfo()[2], $query, $params);
387
    }
388
389
390
391
    /**
392
     * Throw exception when statement failed using detailed message.
393
     *
394
     * @param string $query  query to execute
395
     * @param array  $params to match ? in statement
396
     *
397
     * @return void
398
     */
399
    protected function statementException(string $query, array $params) : void
400
    {
401
        $this->createException($this->stmt->errorInfo()[2], $query, $params);
402
    }
403
404
405
406
    /**
407
     * Return last insert id from autoincremented key on INSERT.
408
     *
409
     * @return integer as last insert id.
410
     */
411
    public function lastInsertId() : int
412
    {
413
        return $this->pdo->lastInsertId();
414
    }
415
416
417
418
    /**
419
    * Return rows affected of last INSERT, UPDATE, DELETE
420
    *
421
    * @return integer as rows affected on last statement
422
    */
423
    public function rowCount() : int
424
    {
425
        return $this->stmt->rowCount();
426
    }
427
}
428