Added Db class
authorIgor Scheller <igor.scheller@igorshp.de>
Fri, 1 Dec 2017 14:41:39 +0000 (15:41 +0100)
committerIgor Scheller <igor.scheller@igorshp.de>
Fri, 1 Dec 2017 14:41:39 +0000 (15:41 +0100)
config.sample.php
include/cryptography.php
include/database.php
include/earnings.php
include/events.php
include/expenses.php
include/finances.php
include/members.php
include/payments.php
src/Database/Db.php [new file with mode: 0644]

index 6d3f34a..2f2bce2 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 
 return [
-    'db'            => [
+    'database'      => [
         'user'     => 'memberdb',
         'password' => 'password',
         'database' => 'memberdb',
index 484fb82..5615716 100644 (file)
@@ -62,7 +62,7 @@ function db_add_crypto_key($key)
 {
     $key['created_at'] = db_unixtime2datetime(time());
     $key['modified_at'] = db_unixtime2datetime(time());
-    return (db_insert_single('cryptography', $key));
+    return (db_insert('cryptography', $key));
 }
 
 /* }}} */
index d9cda8d..d54354d 100644 (file)
 <?php
 
-/* DATABASE TABLE NAMES {{{ */
-
 use MemberDB\Config\Config;
-
-/* }}} */
-
-
-/* COMPATIBILITY functions {{{ */
-if (function_exists('mysql_set_charset') === false) {
-    function mysql_set_charset($charset, $link_identifier = null)
-    {
-        if ($link_identifier == null) {
-            return mysql_query('SET NAMES "' . $charset . '"');
-        }
-        return mysql_query('SET NAMES "' . $charset . '"', $link_identifier);
-    }
-}
-/* }}} */
+use MemberDB\Database\Db;
 
 function db_connect()
 {
-    global $dbh;
     $config = Config::getInstance();
-    $db = $config->get('db');
-    if (!($dbh = mysql_connect($db['host'], $db['user'], $db['password'], true))) {
-        throw new Exception('<html><body>Konnte keine Verbindung zur Datenbank herstellen.</body></html>');
-    }
-    mysql_set_charset('utf8', $dbh);
-    if (!mysql_select_db($db['database'])) {
-        throw new Exception('<html><body>Eine Verbindung zur Datenbank konnte hergestellt werden, aber die angegebene Datenbank konnte nicht ausgewählt werden.</body></html>');
+    $success = Db::connect(
+        'mysql:host=' . $config->get('database')['host'] . ';dbname=' . $config->get('database')['database'] . ';charset=utf8',
+        $config->get('database')['user'],
+        $config->get('database')['password']
+    );
+    if (!$success) {
+        echo '<html><body>Konnte keine Verbindung zur Datenbank herstellen.</body></html>';
+        exit();
     }
-}
 
-// escape value for usage in mysql query
-function db_escape($value)
-{
-    global $dbh;
-    return mysql_real_escape_string($value, $dbh);
-}
-
-// escape each value of an array for usage in mysql query
-function db_escape_array($arr)
-{
-    global $dbh;
-    foreach ($arr as $key => $val) {
-        $arr[$key] = mysql_real_escape_string($val, $dbh);
-    }
-    return $arr;
+    Db::getPdo()->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+    Db::getPdo()->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
 }
 
 // select one row
-function db_select_single($query)
+function db_select_single($query, $bindings = [])
 {
-    global $dbh;
-    $res = mysql_query($query, $dbh);
-    if (!$res) {
-        echo $query, ' ', db_error();
-        return null;
-    }
-    if (mysql_num_rows($res) != 1) {
-        return null;
-    }
-    $row = mysql_fetch_assoc($res);
-    mysql_free_result($res);
-    return $row;
+    return Db::selectOne($query, $bindings);
 }
 
 // select multiple rows
-function db_select_multi($query)
+function db_select_multi($query, $bindings = [])
 {
-    global $dbh;
-    $res = mysql_query($query, $dbh);
-    if (!$res) {
-        echo $query, ' ', db_error();
-        return null;
-    }
-    if (mysql_num_rows($res) == 0) {
-        return array();
-    }
-    $ret = array();
-    while ($row = mysql_fetch_assoc($res)) {
-        $ret[] = $row;
-    }
-    mysql_free_result($res);
-    return $ret;
+    return Db::select($query, $bindings);
 }
 
 // insert one row
-function db_insert_single($table, $fields, $ignore = false)
+function db_insert($table, $fields, $ignore = false)
 {
-    global $dbh;
-    $query = 'INSERT INTO ' . $table;
-    if ($ignore) {
-        $query = 'INSERT IGNORE INTO ' . $table;
-    }
-    $query .= ' (' . join(',', array_keys($fields)) . ') VALUES (\'' . join('\', \'', db_escape_array($fields)) . '\')';
-    $res = mysql_query($query, $dbh);
-    if (!$res) {
-        echo $query, ' ', db_error();
+    $query = sprintf('INSERT %s INTO %s', ($ignore ? 'IGNORE' : ''), $table);
+
+    $replacements = [];
+    foreach ($fields as $field) {
+        $replacements[] = ':' . $field;
     }
-    return $res;
+
+    $query .= ' (`' . join('`,`', array_keys($fields)) . '`) VALUES (' . join(',', $replacements) . ')';
+
+    return Db::insert($query, $fields);
 }
 
 // get automatically generated id of last record
-function db_insert_id()
+function db_insert_id($name = 'id')
 {
-    global $dbh;
-    return mysql_insert_id($dbh);
+    return Db::getPdo()->lastInsertId($name);
 }
 
 // begin transaction
 function db_begin()
 {
-    global $dbh;
-    return mysql_query('BEGIN');
+    return Db::getPdo()->beginTransaction();
 }
 
 // commit transaction
 function db_commit()
 {
-    global $dbh;
-    return mysql_query('COMMIT');
+    return Db::getPdo()->commit();
 }
 
 // rollback transaction
 function db_rollback()
 {
-    global $dbh;
-    return mysql_query('ROLLBACK');
+    Db::getPdo()->rollBack();
 }
 
 // update multiple rows
-function db_update_multi($table, $fields, $where = '', $special = '')
+function db_update($table, $fields, $where = '', $special = '', $limit = 'LIMIT 1')
 {
-    global $dbh;
-    $query = 'UPDATE ' . $table . ' SET ';
-    $first = true;
-    foreach ($fields as $name => $value) {
-        if (!$first) {
-            $query .= ', ';
-        } else {
-            $first = false;
-        }
-        $query .= '`' . $name . '`=\'' . db_escape($value) . '\'';
-    }
-    $query .= $special;
-    if (!empty($where)) {
-        $query .= ' WHERE ' . $where;
-    }
-    $res = mysql_query($query, $dbh);
-    if (!$res) {
-        echo $query, ' ', db_error();
-    }
-    return $res;
-}
+    $query = 'UPDATE `' . $table . '` SET ';
 
-// update one rows
-function db_update_single($table, $fields, $where = '', $special = '')
-{
-    global $dbh;
-    $query = 'UPDATE ' . $table . ' SET ';
-    $first = true;
-    foreach ($fields as $name => $value) {
-        if (!$first) {
-            $query .= ', ';
-        } else {
-            $first = false;
-        }
-        $query .= '`' . $name . '`=\'' . db_escape($value) . '\'';
+    foreach ($fields as $field) {
+        $query .= '`' . $field . '` = ? ';
     }
-    $query .= $special;
+
+    $query .= $special . ' ';
     if (!empty($where)) {
-        $query .= ' WHERE ' . $where;
-    }
-    $query .= ' LIMIT 1';
-    $res = mysql_query($query, $dbh);
-    if (!$res) {
-        echo $query, ' ', db_error();
+        $query .= 'WHERE ' . $where . ' ';
     }
-    return $res;
+
+    $query .= $limit;
+
+    return Db::update($query, array_values($fields));
 }
 
 // delete one row
-function db_delete_single($table, $where)
+function db_delete($table, $where, $bindings = [])
 {
-    global $dbh;
-    $query = 'DELETE FROM ' . $table . ' WHERE ' . $where . ' LIMIT 1';
-    $res = mysql_query($query, $dbh);
-    if (!$res) {
-        return null;
-    }
-    return (mysql_affected_rows($dbh));
+    $query = sprintf('DELETE FROM `%s` WHERE %s LIMIT 1', $table, $where);
+
+    return Db::delete($query, $bindings);
 }
 
 // get error message
 function db_error()
 {
-    global $dbh;
-    return mysql_error($dbh);
+    return Db::getError()[2];
 }
 
 function db_unixtime2date($unixtime)
@@ -215,8 +118,3 @@ function db_unixtime2datetime($unixtime)
 {
     return strftime('%Y-%m-%d %H:%M:%S', $unixtime);
 }
-
-function db_datetime2unixtime($datetime)
-{
-    return strtotime($datetime);
-}
index 8de3d67..22c28c2 100644 (file)
@@ -17,8 +17,7 @@ $EARNING_TYPES = array(
 
 function db_get_earning_with_id($earning_id)
 {
-    $sql = 'SELECT * FROM `earnings` WHERE id=\'%d\' LIMIT 1';
-    return db_select_single(sprintf($sql, db_escape($earning_id)));
+    return db_select_single('SELECT * FROM `earnings` WHERE id=? LIMIT 1', [$earning_id]);
 }
 
 function db_create_earning($earning)
@@ -28,7 +27,7 @@ function db_create_earning($earning)
     $earning['value'] = ui_money2float($earning['value']);
     $earning['created_at'] = db_unixtime2datetime(time());
     $earning['modified_at'] = db_unixtime2datetime(time());
-    return (db_insert_single('earnings', $earning));
+    return (db_insert('earnings', $earning));
 }
 
 function db_change_earning($earning)
@@ -38,17 +37,14 @@ function db_change_earning($earning)
     $earning['date'] = db_unixtime2datetime(ui_date2unixtime($earning['date']));
     $earning['value'] = ui_money2float($earning['value']);
     $earning['modified_at'] = db_unixtime2datetime(time());
-    return db_update_single('earnings', $earning, sprintf('id=\'%d\'', db_escape($id)));
+    return db_update('earnings', $earning, sprintf("id='%d'", (int)$id));
 }
 
 function db_delete_earning($earning)
 {
-    return db_delete_single('earnings', sprintf('id=\'%d\'', db_escape($earning['id'])));
+    return db_delete('earnings', 'id=?', [$earning['id']]);
 }
 
-/* }}} */
-
-
 function validate_earning($userdata, &$dbdata, &$validation)
 {/*{{{*/
     global $EARNING_TYPES, $ACCOUNT_TYPES;
@@ -225,8 +221,11 @@ function action_create_earnings()
     foreach ($_POST['earnings'] as $post_data) {
         $member['modified_at'] = db_unixtime2datetime(time());
         $member['directdebit'] = 3; // RCUR
-        db_update_single('members', $member,
-            sprintf('directdebit=\'2\' AND id=\'%d\'', db_escape($post_data['member_id'])));
+        db_update(
+            'members',
+            $member,
+            sprintf("directdebit=2 AND id='%d'", (int)$post_data['member_id'])
+        );
     }
 
     redirect(link_to('finances'));
index 6da4c64..8201349 100644 (file)
@@ -28,7 +28,7 @@ function db_create_event($event)
     $event['fee'] = ui_money2float($event['fee']);
     $event['created_at'] = db_unixtime2datetime(time());
     $event['modified_at'] = db_unixtime2datetime(time());
-    return (db_insert_single('events', $event));
+    return (db_insert('events', $event));
 }
 
 function db_change_event($event)
@@ -49,24 +49,25 @@ function db_change_event($event)
     if (!isset($event['payment_interval'])) {
         $special .= ',`payment_interval`=NULL';
     }
-    return db_update_single('events', $event, sprintf('id=\'%d\'', db_escape($id)), $special);
+    return db_update('events', $event, sprintf("id='%d'", (int)$id), $special);
 }
 
 function db_delete_event($event)
 {
-    return db_delete_single('events', sprintf('id=\'%d\'', db_escape($event['id'])));
+    return db_delete('events', 'id=?', [$event['id']]);
 }
 
 function db_get_events_for_member($member_id, $unixtime_start = null, $unixtime_end = null)
 {
+    // @TODO: Change to prepared statement
     $conditions = array(
-        sprintf('member_id=\'%d\'', db_escape($member_id))
+        sprintf("member_id=%d", (int)$member_id)
     );
     if (isset($unixtime_start)) {
-        $conditions[] = sprintf('event_date>=\'%s\'', db_unixtime2date($unixtime_start));
+        $conditions[] = sprintf("event_date>='%s'", db_unixtime2date($unixtime_start));
     }
     if (isset($unixtime_end)) {
-        $conditions[] = sprintf('event_date<=\'%s\'', db_unixtime2date($unixtime_end));
+        $conditions[] = sprintf("event_date<='%s'", db_unixtime2date($unixtime_end));
     }
 
     $sql = 'SELECT * FROM `events` WHERE %s ORDER BY event_date ASC';
@@ -75,14 +76,14 @@ function db_get_events_for_member($member_id, $unixtime_start = null, $unixtime_
 
 function db_get_event_with_member_and_date($member_id, $event_date)
 {
-    $sql = 'SELECT * FROM `events` WHERE member_id=\'%d\' AND event_date=\'%s\' LIMIT 1';
-    return db_select_single(sprintf($sql, db_escape($member_id), db_escape($event_date)));
+    $sql = 'SELECT * FROM `events` WHERE member_id=? AND event_date=? LIMIT 1';
+    return db_select_single($sql, [$member_id, $event_date]);
 }
 
 function db_get_event_with_id($event_id)
 {
-    $sql = 'SELECT * FROM `events` WHERE id=\'%d\' LIMIT 1';
-    return db_select_single(sprintf($sql, db_escape($event_id)));
+    $sql = 'SELECT * FROM `events` WHERE id=? LIMIT 1';
+    return db_select_single($sql, [$event_id]);
 }
 
 /* }}} */
index 3df08bf..17939d6 100644 (file)
@@ -14,8 +14,8 @@ $EXPENSE_TYPES = array(
 
 function db_get_expense_with_id($expense_id)
 {
-    $sql = 'SELECT * FROM `expenses` WHERE id=\'%d\' LIMIT 1';
-    return db_select_single(sprintf($sql, db_escape($expense_id)));
+    $sql = 'SELECT * FROM `expenses` WHERE id=? LIMIT 1';
+    return db_select_single($sql, [$expense_id]);
 }
 
 function db_create_expense($expense)
@@ -25,7 +25,7 @@ function db_create_expense($expense)
     $expense['value'] = ui_money2float($expense['value']);
     $expense['created_at'] = db_unixtime2datetime(time());
     $expense['modified_at'] = db_unixtime2datetime(time());
-    return (db_insert_single('expenses', $expense));
+    return (db_insert('expenses', $expense));
 }
 
 function db_change_expense($expense)
@@ -35,12 +35,12 @@ function db_change_expense($expense)
     $expense['date'] = db_unixtime2datetime(ui_date2unixtime($expense['date']));
     $expense['value'] = ui_money2float($expense['value']);
     $expense['modified_at'] = db_unixtime2datetime(time());
-    return db_update_single('expenses', $expense, sprintf('id=\'%d\'', db_escape($id)));
+    return db_update('expenses', $expense, sprintf('id=%d', (int)$id));
 }
 
 function db_delete_expense($expense)
 {
-    return db_delete_single('expenses', sprintf('id=\'%d\'', db_escape($expense['id'])));
+    return db_delete('expenses', 'id=?', $expense['id']);
 }
 
 /* }}} */
index 12fec88..b314aa7 100644 (file)
@@ -5,8 +5,8 @@ use MemberDB\Config\Config;
 
 function finance_get_total_paid_fees()
 {
-    $paid = db_select_single('SELECT SUM(value) AS paid_fees FROM `earnings` WHERE type IN(\'old_fee\', \'fee\') AND `status`=\'paid\'');
-    $refunded = db_select_single('SELECT SUM(value) AS refunded_fees FROM `expenses` WHERE type IN(\'member_fee\') AND `status`=\'paid\'');
+    $paid = db_select_single("SELECT SUM(value) AS paid_fees FROM `earnings` WHERE type IN('old_fee', 'fee') AND `status`='paid'");
+    $refunded = db_select_single("SELECT SUM(value) AS refunded_fees FROM `expenses` WHERE type IN('member_fee') AND `status`='paid'");
 
     return bcsub($paid['paid_fees'], $refunded['refunded_fees']);
 }
@@ -17,11 +17,10 @@ function finance_get_paid_fees_for_member($member_id, $old_fees = false)
     // $old_fees: Beiträge die nicht durch Lastschrift eingezogen werden.
     //            Falls zu viel bezahlt, müssen diese auf die per Lastschrift
     //            eingezogenen Beiträge angerechnet werden.
-    $row = db_select_single(sprintf(
-        'SELECT SUM(value) AS paid_fees FROM `earnings` WHERE member_id=\'%d\' AND type=\'%s\' AND `status`=\'paid\'',
-        db_escape($member_id),
-        $old_fees ? 'old_fee' : 'fee'
-    ));
+    $row = db_select_single(
+        'SELECT SUM(value) AS paid_fees FROM `earnings` WHERE member_id=? AND type=? AND `status`=\'paid\'',
+        [$member_id, $old_fees ? 'old_fee' : 'fee']
+    );
 
     $paid_fees = $row['paid_fees'];
 
@@ -38,22 +37,22 @@ function finance_get_paid_fees_for_member($member_id, $old_fees = false)
     }
 
 
-    $refunded = db_select_single(sprintf(
-        'SELECT SUM(value) AS refunded_fees FROM `expenses` WHERE member_id=\'%d\' AND type IN(\'member_fee\') AND `status`=\'paid\'',
-        db_escape($member_id)
-    ));
+    $refunded = db_select_single(
+        "SELECT SUM(value) AS refunded_fees FROM `expenses` WHERE member_id=? AND type IN('member_fee') AND `status`='paid'",
+        [$member_id]
+    );
     return bcsub($paid_fees, $refunded['refunded_fees']);
 }
 
 function finance_list_paid_fees_for_member($member_id)
 {
-    return db_select_multi(sprintf('
-               (SELECT id, date, type, status, account, value, member_id, description, created_at, modified_at FROM `earnings` WHERE member_id=\'%1$d\' AND type IN(\'old_fee\', \'fee\'))
+    return db_select_multi("
+               (SELECT id, date, type, status, account, value, member_id, description, created_at, modified_at FROM `earnings` WHERE member_id=? AND type IN('old_fee', 'fee'))
                UNION
-               (SELECT id, date, type, status, account, value*-1, member_id, description, created_at, modified_at FROM `expenses` WHERE member_id=\'%1$d\' AND type IN(\'member_fee\'))
-               ORDER BY `date` DESC',
-        db_escape($member_id)
-    ));
+               (SELECT id, date, type, status, account, value*-1, member_id, description, created_at, modified_at FROM `expenses` WHERE member_id=? AND type IN('member_fee'))
+               ORDER BY `date` DESC",
+        [$member_id, $member_id]
+    );
 }
 
 function action_finances()
index b965c37..df11e84 100644 (file)
@@ -14,7 +14,7 @@ function db_create_member($member)
     unset($member['id']);
     $member['created_at'] = db_unixtime2datetime(time());
     $member['modified_at'] = db_unixtime2datetime(time());
-    if (!db_insert_single('members', $member)) {
+    if (!db_insert('members', $member)) {
         return false;
     }
     return db_insert_id();
@@ -25,18 +25,18 @@ function db_change_member($member)
     $id = $member['id'];
     unset($member['id']);
     $member['modified_at'] = db_unixtime2datetime(time());
-    return db_update_single('members', $member, sprintf('id=\'%d\'', db_escape($id)));
+    return db_update('members', $member, sprintf('id=%d', (int)$id));
 }
 
 function db_get_members()
 {
-    return db_select_multi('SELECT * FROM `members` ORDER BY NUMBER ASC');
+    return db_select_multi('SELECT * FROM `members` ORDER BY `number` ASC');
 }
 
 function db_get_member_with_id($member_id)
 {
-    $sql = 'SELECT * FROM `m̀embers` WHERE id=\'%d\' LIMIT 1';
-    return db_select_single(sprintf($sql, db_escape($member_id)));
+    $sql = 'SELECT * FROM `m̀embers` WHERE id=? LIMIT 1';
+    return db_select_single($sql, [$member_id]);
 }
 
 function db_get_next_free_member_number()
@@ -66,8 +66,7 @@ function db_get_next_free_member_number()
 
 function db_get_member_with_number($member_number)
 {
-    $sql = 'SELECT * FROM `members` WHERE NUMBER=\'%d\' LIMIT 1';
-    return db_select_single(sprintf($sql, db_escape($member_number)));
+    return db_select_single('SELECT * FROM `members` WHERE NUMBER=? LIMIT 1', [$member_number]);
 }
 
 /* }}} */
index 7fb2425..5156f27 100644 (file)
@@ -16,22 +16,22 @@ function db_get_payments($offset, $count, $where)
     } elseif (isset($offset)) {
         $limit = sprintf(' LIMIT %d', (int)$count);
     }
-    $sql = '
+    $sql = "
         SELECT t.*, members.nickname AS nickname
         FROM (
-            (SELECT \'earning\' AS ptype, `earnings`.* FROM `earnings` `earnings`)
+            (SELECT 'earning' AS ptype, `earnings`.* FROM `earnings` `earnings`)
             UNION
-            (SELECT \'expense\', expenses.* FROM `expenses` `earnings`)
+            (SELECT 'expense', expenses.* FROM `expenses` `earnings`)
         ) AS t
         LEFT JOIN `members` ON t.member_id=members.id
         ORDER BY `date` DESC, t.id DESC
-    ' . $limit;
+    " . $limit;
     return db_select_multi(sprintf($sql, $where));
 }
 
 function db_get_finished_payments($offset, $count)
 {
-    $where = ' WHERE status=\'paid\'';
+    $where = " WHERE status='paid'";
     $payments = db_get_payments($offset, $count, $where);
 
     $bank = 0;
@@ -40,13 +40,13 @@ function db_get_finished_payments($offset, $count)
     if ($count != 0) { // XXX TODO hä? - An dieser Stelle gäbe es sinnigere Bedinungen
         $limit = sprintf(' LIMIT %d,18446744073709551615',
             (int)($count + $offset)); // SELECT mit Offset aber ohne Limit
-        $sql = 'SELECT SUM(IF(`account`=\'bank\', `value`, 0)) AS `bank`, SUM(IF(`account`=\'cash\', `value`, 0)) AS `cash`, SUM(`value`) AS `total` 
+        $sql = "SELECT SUM(IF(`account`='bank', `value`, 0)) AS `bank`, SUM(IF(`account`='cash', `value`, 0)) AS `cash`, SUM(`value`) AS `total` 
                 FROM (
                     SELECT `value`, `account` 
-                    FROM (( SELECT `value`,                 `id`, `date`, `account` FROM `earnings` %1$s) 
-                    UNION ( SELECT `value` * -1 AS `value`, `id`, `date`, `account` FROM `expenses` %1$s)
+                    FROM (( SELECT `value`,                 `id`, `date`, `account` FROM `earnings` %1\$s) 
+                    UNION ( SELECT `value` * -1 AS `value`, `id`, `date`, `account` FROM `expenses` %1\$s)
                     ) AS t
-                    ORDER BY `date` DESC, t.id DESC' . $limit . '
+                    ORDER BY `date` DESC, t.id DESC" . $limit . '
                 ) AS u';
         $row = db_select_single(sprintf($sql, $where));
         $bank = $row['bank'];
@@ -78,7 +78,7 @@ function db_get_finished_payments($offset, $count)
 
 function db_count_finished_payments()
 {
-    $where = ' WHERE status=\'paid\'';
+    $where = " WHERE status='paid'";
     $sql = 'SELECT count(*) AS `count` FROM ((SELECT * FROM `earnings` %1$s) UNION (SELECT * FROM `expenses` %1$s)) AS t';
     $row = db_select_single(sprintf($sql, $where));
     return $row['count'];
@@ -86,7 +86,7 @@ function db_count_finished_payments()
 
 function db_get_open_payments($offset, $count)
 {
-    return db_get_payments($offset, $count, ' WHERE status=\'open\'');
+    return db_get_payments($offset, $count, " WHERE status='open'");
 }
 
 /* }}} */
diff --git a/src/Database/Db.php b/src/Database/Db.php
new file mode 100644 (file)
index 0000000..f7189b7
--- /dev/null
@@ -0,0 +1,188 @@
+<?php
+
+namespace MemberDB\Database;
+
+use PDO;
+use PDOException;
+use PDOStatement;
+
+class Db
+{
+    /** @var PDO */
+    protected static $db;
+
+    /** @var PDOStatement */
+    protected static $stm = null;
+
+    /** @var bool */
+    protected static $lastStatus = true;
+
+    /**
+     * Connect to database
+     *
+     * @param string $dsn
+     * @param string $username
+     * @param string $password
+     * @param array  $options
+     * @return bool
+     */
+    public static function connect($dsn, $username = null, $password = null, $options = [])
+    {
+        try {
+            self::$db = new PDO($dsn, $username, $password, $options);
+        } catch (PDOException $e) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Run a prepared query
+     *
+     * @param string $query
+     * @param array  $bindings
+     * @return PDOStatement
+     */
+    public static function query($query, array $bindings = [])
+    {
+        self::$stm = self::$db->prepare($query);
+        self::$lastStatus = self::$stm->execute($bindings);
+
+        return self::$stm;
+    }
+
+    /**
+     * Run a sql query
+     *
+     * @param string $query
+     * @return bool
+     */
+    public static function unprepared($query)
+    {
+        self::$stm = self::$db->query($query);
+        self::$lastStatus = (self::$stm instanceof PDOStatement);
+
+        return self::$lastStatus;
+    }
+
+    /**
+     * Run a select query
+     *
+     * @param string $query
+     * @param array  $bindings
+     * @return array
+     */
+    public static function select($query, array $bindings = [])
+    {
+        self::query($query, $bindings);
+
+        return self::$stm->fetchAll(PDO::FETCH_ASSOC);
+    }
+
+    /**
+     * Run a select query and return only the first result or null if no result is found.
+     *
+     * @param string $query
+     * @param array  $bindings
+     * @return array|null
+     */
+    public static function selectOne($query, array $bindings = [])
+    {
+        $result = self::select($query, $bindings);
+
+        if (empty($result)) {
+            return null;
+        }
+
+        return array_shift($result);
+    }
+
+    /**
+     * Run an insert query
+     *
+     * @param string $query
+     * @param array  $bindings
+     * @return int Row count
+     */
+    public static function insert($query, array $bindings = [])
+    {
+        self::query($query, $bindings);
+
+        return self::$stm->rowCount();
+    }
+
+    /**
+     * Run an update query
+     *
+     * @param string $query
+     * @param array  $bindings
+     * @return int
+     */
+    public static function update($query, array $bindings = [])
+    {
+        self::query($query, $bindings);
+
+        return self::$stm->rowCount();
+    }
+
+    /**
+     * Run a delete query
+     *
+     * @param string $query
+     * @param array  $bindings
+     * @return int
+     */
+    public static function delete($query, array $bindings = [])
+    {
+        self::query($query, $bindings);
+
+        return self::$stm->rowCount();
+    }
+
+    /**
+     * Run a single statement
+     *
+     * @param string $query
+     * @param array  $bindings
+     * @return bool
+     */
+    public static function statement($query, array $bindings = [])
+    {
+        self::query($query, $bindings);
+
+        return self::$lastStatus;
+    }
+
+    /**
+     * Returns the last error
+     *
+     * @return array
+     */
+    public static function getError()
+    {
+        if (!self::$stm instanceof PDOStatement) {
+            return [-1, null, null];
+        }
+
+        return self::$stm->errorInfo();
+    }
+
+    /**
+     * Get the PDO instance
+     *
+     * @return PDO
+     */
+    public static function getPdo()
+    {
+        return self::$db;
+    }
+
+    /**
+     * @return PDOStatement|false|null
+     */
+    public static function getStm()
+    {
+        return self::$stm;
+    }
+}