MySQL::autocommit()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * DronePHP (http://www.dronephp.com)
4
 *
5
 * @link      http://github.com/Pleets/DronePHP
6
 * @copyright Copyright (c) 2016-2018 Pleets. (http://www.pleets.org)
7
 * @license   http://www.dronephp.com/license
8
 * @author    Darío Rivera <[email protected]>
9
 */
10
11
namespace Drone\Db\Driver;
12
13
/**
14
 * MySQL class
15
 *
16
 * This is a database driver class to connect to MySQL
17
 */
18
class MySQL extends AbstractDriver implements DriverInterface
19
{
20
    /**
21
     * {@inheritdoc}
22
     *
23
     * @var object
24
     */
25
    protected $dbconn;
26
27
    /**
28
     * {@inheritdoc}
29
     *
30
     * @param array $options
31
     */
32 21
    public function __construct($options)
33
    {
34 21
        $this->driverName = 'Mysqli';
35
36 21
        if (!array_key_exists("dbchar", $options)) {
37
            $options["dbchar"] = "utf8";
38
        }
39
40 21
        parent::__construct($options);
41
42 21
        $auto_connect = array_key_exists('auto_connect', $options) ? $options["auto_connect"] : true;
43
44 21
        if ($auto_connect) {
45 11
            $this->connect();
46
        }
47 21
    }
48
49
    /**
50
     * Connects to database
51
     *
52
     * @throws RuntimeException
53
     * @throws Exception\ConnectionException
54
     *
55
     * @return \mysqli
56
     */
57 18
    public function connect()
58
    {
59 18
        if (!extension_loaded('mysqli')) {
60
            throw new \RuntimeException("The Mysqli extension is not loaded");
61
        }
62
63 18
        if (!is_null($this->dbport) && !empty($this->dbport)) {
0 ignored issues
show
introduced by
The condition is_null($this->dbport) is always false.
Loading history...
64 18
            $conn = @new \mysqli($this->dbhost, $this->dbuser, $this->dbpass, $this->dbname, $this->dbport);
65
        } else {
66
            $conn = @new \mysqli($this->dbhost, $this->dbuser, $this->dbpass, $this->dbname);
67
        }
68
69 18
        if ($conn->connect_errno) {
70
            /*
71
             * Use ever mysqli_connect_errno() and mysqli_connect_error()
72
             * over $this->dbconn->errno and $this->dbconn->error to prevent
73
             * the warning message "Property access is not allowed yet".
74
             */
75 2
            throw new Exception\ConnectionException(mysqli_connect_error(), mysqli_connect_errno());
76
        } else {
77 16
            $this->dbconn = $conn;
78 16
            $this->dbconn->set_charset($this->dbchar);
79
        }
80
81 16
        return $this->dbconn;
82
    }
83
84
    /**
85
     * Excecutes a statement
86
     *
87
     * @param string $sql
88
     * @param array $params
89
     *
90
     * @throws RuntimeException
91
     * @throws Exception\InvalidQueryException
92
     *
93
     * @return \mysqli_result
94
     */
95 23
    public function execute($sql, array $params = [])
96
    {
97 23
        $this->numRows = 0;
98 23
        $this->numFields = 0;
99 23
        $this->rowsAffected = 0;
100
101 23
        $this->arrayResult = null;
102
103
        # Bound variables
104 23
        if (count($params)) {
105 9
            $this->result = $stmt = @$this->dbconn->prepare($sql);
106
107 9
            if (!$stmt) {
108
                $this->error($this->dbconn->errno, $this->dbconn->error);
0 ignored issues
show
Bug introduced by
The method error() does not exist on Drone\Db\Driver\MySQL. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

108
                $this->/** @scrutinizer ignore-call */ 
109
                       error($this->dbconn->errno, $this->dbconn->error);
Loading history...
109
                throw new Exception\InvalidQueryException($this->dbconn->error, $this->dbconn->errno);
110
            }
111
112 9
            $param_values = array_values($params);
113
114 9
            $n_params = count($param_values);
115 9
            $bind_values = [];
116 9
            $bind_types = "";
117
118 9
            for ($i = 0; $i < $n_params; $i++) {
119 9
                if (is_string($param_values[$i])) {
120 5
                    $bind_types .= 's';
121 9
                } elseif (is_float($param_values[$i])) {
122
                    $bind_types .= 'd';
123
                } else {
124
                    # [POSSIBLE BUG] - To Future revision (What about non-string and non-decimal types ?)
125 9
                    $bind_types .= 's';
126
                }
127
128 9
                $bind_values[] = '$param_values[' . $i . ']';
129
            }
130
131 9
            $values = implode(', ', $bind_values);
132 9
            eval('$stmt->bind_param(\'' . $bind_types . '\', ' . $values . ');');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
133
134 9
            $r = $stmt->execute();
135
        } else {
136 16
            $prev_error_handler = set_error_handler(['\Drone\Error\ErrorHandler', 'errorControlOperator'], E_ALL);
137
138
            // may be throw a Fatal error (Ex: Maximum execution time)
139 16
            $r = $this->result = $this->dbconn->query($sql);
140
141 16
            set_error_handler($prev_error_handler);
142
        }
143
144 23
        if (!$r) {
145 3
            $this->error($this->dbconn->errno, $this->dbconn->error);
146 3
            throw new Exception\InvalidQueryException($this->dbconn->error, $this->dbconn->errno);
147
        }
148
149 21
        $is_stmt_result = is_object($this->result) && get_class($this->result) == 'mysqli_stmt';
150
151 21
        if ($is_stmt_result) {
152 9
            $this->rowsAffected = $this->result->affected_rows;
153
154 9
            $res = $this->result->get_result();
155
156
            /*
157
             * if $res is false then there aren't results.
158
             * It is useful to prevent rollback transactions on insert statements because
159
             * insert statement do not free results.
160
             */
161 9
            if ($res) {
162 2
                $this->result = $res;
163
            }
164
        }
165
166
        # identify SELECT, SHOW, DESCRIBE or EXPLAIN queries
167 21
        if (is_object($this->result) && property_exists($this->result, 'num_rows')) {
168 16
            $this->numRows = $this->result->num_rows;
169
        } else {
170 13
            if (property_exists($this->dbconn, 'affected_rows') && !$is_stmt_result) {
171 13
                $this->rowsAffected = $this->dbconn->affected_rows;
172
            }
173
        }
174
175
        # affected_rows return the same of num_rows on select statements!
176 21
        if ($this->numRows > 0) {
177 9
            $this->rowsAffected = 0;
178
        }
179
180 21
        if (property_exists($this->dbconn, 'field_count')) {
181 21
            $this->numFields = $this->dbconn->field_count;
182
        }
183
184 21
        if ($this->transac_mode) {
185 2
            $this->transac_result = is_null($this->transac_result)
0 ignored issues
show
introduced by
The condition is_null($this->transac_result) is always false.
Loading history...
186 2
                ? $this->result
187 1
                : $this->transac_result && $this->result;
188
        }
189
        /*
190
         * Because mysqli_query() returns FALSE on failure, a mysqli_result object for
191
         * SELECT, SHOW, DESCRIBE or EXPLAIN queries, and TRUE for other successful queries,
192
         * it should be handled to return only objects or resources.
193
         *
194
         * Ref: http://php.net/manual/en/mysqli.query.php
195
         */
196 21
        return is_bool($this->result) ? $this->dbconn : $this->result;
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202 4
    public function commit()
203
    {
204 4
        return $this->dbconn->commit();
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     */
210 2
    public function rollback()
211
    {
212 2
        return $this->dbconn->rollback();
213
    }
214
215
    /**
216
     * {@inheritdoc}
217
     */
218 3
    public function disconnect()
219
    {
220 3
        parent::disconnect();
221
222 2
        if ($this->dbconn->close()) {
223 2
            $this->dbconn = null;
224
225 2
            return true;
226
        }
227
228
        return false;
229
    }
230
231
    /**
232
     * {@inheritdoc}
233
     */
234 4
    public function autocommit($value)
235
    {
236 4
        parent::autocommit($value);
237 4
        $this->dbconn->autocommit($value);
238 4
    }
239
240
    /**
241
     * Returns an array with the rows fetched
242
     *
243
     * @throws LogicException
244
     *
245
     * @return array
246
     */
247 10
    protected function toArray()
248
    {
249 10
        $data = [];
250
251 10
        if ($this->result && !is_bool($this->result)) {
252 9
            while ($row = $this->result->fetch_array(MYSQLI_BOTH)) {
253 9
                $data[] = $row;
254
            }
255
        } else {             # This error is thrown because of 'execute' method has not been executed.
256 1
            throw new \LogicException('There are not data in the buffer!');
257
        }
258
259 9
        $this->arrayResult = $data;
260
261 9
        return $data;
262
    }
263
264
    /**
265
     * By default __destruct() disconnects to database
266
     *
267
     * @return null
268
     */
269 18
    public function __destruct()
270
    {
271
        # prevent "Property access is not allowed yet" with @ on failure connections
272 18
        if ($this->dbconn !== false && !is_null($this->dbconn)) {
273 13
            @$this->dbconn->close();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for close(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

273
            /** @scrutinizer ignore-unhandled */ @$this->dbconn->close();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
274
        }
275 18
    }
276
}
277