Completed
Push — master ( 8480f8...ee2fbd )
by Radu
01:36
created

MysqliDatabase::transaction()   A

Complexity

Conditions 5
Paths 14

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 12
c 1
b 0
f 0
dl 0
loc 16
rs 9.5555
cc 5
nc 14
nop 1
1
<?php
2
namespace WebServCo\Framework\Libraries;
3
4
use WebServCo\Framework\Settings;
5
use WebServCo\Framework\Exceptions\DatabaseException;
6
7
final class MysqliDatabase extends \WebServCo\Framework\AbstractLibrary implements
8
    \WebServCo\Framework\Interfaces\DatabaseInterface
9
{
10
    protected $db;
11
    protected $stmt;
12
    protected $rows;
13
14
    use \WebServCo\Framework\Traits\DatabaseTrait;
15
    use \WebServCo\Framework\Traits\DatabaseAddQueryTrait;
16
    use \WebServCo\Framework\Traits\MysqlDatabaseTrait;
17
18
    protected $mysqliResult;
19
20
    public function __construct($settings = [])
21
    {
22
        parent::__construct($settings);
23
24
        $driver = new \mysqli_driver();
25
        $driver->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT;
26
27
        try {
28
            $this->db = new \mysqli(
29
                $this->setting(sprintf('connection%shost', Settings::DIVIDER), '127.0.0.1'),
30
                $this->setting(sprintf('connection%susername', Settings::DIVIDER), 'root'),
31
                $this->setting(sprintf('connection%spasswd', Settings::DIVIDER), ''),
32
                $this->setting(sprintf('connection%sdbname', Settings::DIVIDER), 'test'),
33
                $this->setting(sprintf('connection%sport', Settings::DIVIDER), 3306)
34
            );
35
            $this->db->set_charset('utf8mb4');
36
        } catch (\Exception $e) { // mysqli_sql_exception/RuntimeException/Exception
37
            throw new DatabaseException($e->getMessage(), $e);
38
        }
39
    }
40
41
    public function affectedRows()
42
    {
43
        if (!($this->stmt instanceof \mysqli_stmt)) {
44
            throw new DatabaseException('No Statement object available.');
45
        }
46
        return $this->stmt->affected_rows;
47
    }
48
49
    public function escape($string)
50
    {
51
        return $this->db->real_escape_string($string);
52
    }
53
54
    public function getColumn($query, $params = [], $columnNumber = 0)
55
    {
56
        $this->query($query, $params);
57
        $this->mysqliResult = $this->stmt->get_result();
58
        $row = $this->mysqliResult->fetch_array(MYSQLI_NUM);
59
        return array_key_exists($columnNumber, $row) ? $row[$columnNumber] : false;
60
    }
61
62
    public function getRow($query, $params = [])
63
    {
64
        $this->query($query, $params);
65
        $this->mysqliResult = $this->stmt->get_result();
66
        return $this->mysqliResult->fetch_assoc();
67
    }
68
69
    public function getRows($query, $params = [])
70
    {
71
        $this->query($query, $params);
72
        $this->mysqliResult = $this->stmt->get_result();
73
        $this->rows = $this->mysqliResult->fetch_all(MYSQLI_ASSOC);
74
        return $this->rows;
75
    }
76
77
    /**
78
     * Get last inserted Id.
79
     *
80
     * https://dev.mysql.com/doc/refman/5.5/en/information-functions.html#function_last-insert-id
81
     * If you insert multiple rows using a single INSERT statement,
82
     * LAST_INSERT_ID() returns the value generated for the first inserted row only.
83
     * The reason for this is to make it possible to reproduce easily the same
84
     * INSERT statement against some other server.
85
     */
86
    public function lastInsertId()
87
    {
88
        return (int) $this->db->insert_id;
89
    }
90
91
    public function numRows()
92
    {
93
        /**
94
         * @TODO Fix.
95
         * "$this->stmt->num_rows" will be 0 because we can't use
96
         * "$this->stmt->store_result();"
97
         * We could count "$this->mysqliResult" but that would mean
98
         * the method will only work if getRow*() was called before.
99
         */
100
        throw new DatabaseException('Method not implemented.');
101
    }
102
103
    public function query($query, $params = [])
104
    {
105
        if (empty($query)) {
106
            throw new DatabaseException('No query specified');
107
        }
108
109
        try {
110
            /**
111
             * For simplicity use statements even for simple queries.
112
             */
113
            $this->stmt = $this->db->prepare($query);
114
            $this->bindParams($params);
115
            $this->stmt->execute();
116
            return $this->stmt;
117
        } catch (\Exception $e) { // mysqli_sql_exception/RuntimeException/Exception
118
            throw new DatabaseException($e->getMessage());
119
        }
120
    }
121
122
    public function transaction($queries)
123
    {
124
        try {
125
            $this->db->autocommit(false);
126
            foreach ($queries as $item) {
127
                if (!isset($item[0])) {
128
                    throw new DatabaseException('No query specified');
129
                }
130
                $params = isset($item[1]) ? $item[1] : [];
131
                $this->query($item[0], $params);
132
            }
133
            $this->db->commit();
134
            return true;
135
        } catch (\Exception $e) { // mysqli_sql_exception/RuntimeException/Exception
136
            $this->db->rollback();
137
            throw new DatabaseException($e->getMessage());
138
        }
139
    }
140
141
    protected function bindParams($params = [])
142
    {
143
        if (empty($params)) {
144
            return false;
145
        }
146
147
        $types = [];
148
        $values = [];
149
        foreach ($params as $item) {
150
            if (is_array($item)) {
151
                foreach ($item as $value) {
152
                    $types[] = $this->getDataType($value);
153
                    $values[] = $value;
154
                }
155
            } else {
156
                $types[] = $this->getDataType($item);
157
                $values[] = $item;
158
            }
159
        }
160
161
        $typeString = implode('', $types);
162
        $args = [
163
            0 => &$typeString,
164
        ];
165
        foreach ($values as &$v) {
166
            $args[] = &$v;
167
        }
168
        $callable = [$this->stmt, 'bind_param'];
169
        if (!is_callable($callable)) {
170
            throw new DatabaseException('Method not found');
171
        }
172
        return call_user_func_array($callable, $args);
173
    }
174
175
    protected function getDataType($variable)
176
    {
177
        $type = gettype($variable);
178
179
        switch ($type) {
180
            case 'integer':
181
                return 'i';
182
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
183
            case 'double':
184
                return 'd';
185
                break;
186
            case 'string':
187
            case 'boolean':
188
            case 'NULL':
189
            case 'array':
190
            case 'object':
191
            case 'resource':
192
            case 'resource (closed)':
193
            case 'unknown type':
194
            default:
195
                return 's';
196
                break;
197
        }
198
    }
199
}
200