Commit 01e6d0bd authored by Josh Campbell's avatar Josh Campbell

Merge pull request #130 from avbdr/master

Avoid out of memory bug in php 5.2 and 5.3 / 	bug #129: Check correct execution status instead of affected rows,
parents c77fd388 5ef4553e
...@@ -188,11 +188,14 @@ class MysqliDb ...@@ -188,11 +188,14 @@ class MysqliDb
* *
* @param string $query Contains a user-provided query. * @param string $query Contains a user-provided query.
* @param array $bindParams All variables to bind to the SQL statment. * @param array $bindParams All variables to bind to the SQL statment.
* @param bool $sanitize If query should be filtered before execution
* *
* @return array Contains the returned rows from the query. * @return array Contains the returned rows from the query.
*/ */
public function rawQuery($query, $bindParams = null) public function rawQuery ($query, $bindParams = null, $sanitize = true)
{ {
$this->_query = $query;
if ($sanitize)
$this->_query = filter_var ($query, FILTER_SANITIZE_STRING, $this->_query = filter_var ($query, FILTER_SANITIZE_STRING,
FILTER_FLAG_NO_ENCODE_QUOTES); FILTER_FLAG_NO_ENCODE_QUOTES);
$stmt = $this->_prepareQuery(); $stmt = $this->_prepareQuery();
...@@ -316,12 +319,13 @@ class MysqliDb ...@@ -316,12 +319,13 @@ class MysqliDb
$this->_query = "UPDATE " . self::$_prefix . $tableName ." SET "; $this->_query = "UPDATE " . self::$_prefix . $tableName ." SET ";
$stmt = $this->_buildQuery(null, $tableData); $stmt = $this->_buildQuery (null, $tableData);
$stmt->execute(); $status = $stmt->execute();
$this->_stmtError = $stmt->error;
$this->reset(); $this->reset();
$this->_stmtError = $stmt->error;
$this->count = $stmt->affected_rows;
return ($stmt->affected_rows > 0); return $status;
} }
/** /**
...@@ -521,25 +525,38 @@ class MysqliDb ...@@ -521,25 +525,38 @@ class MysqliDb
* *
* @param string Variable value * @param string Variable value
*/ */
protected function _bindParam($value) protected function _bindParam($value) {
{
$this->_bindParams[0] .= $this->_determineType ($value); $this->_bindParams[0] .= $this->_determineType ($value);
array_push ($this->_bindParams, $value); array_push ($this->_bindParams, $value);
} }
/**
* Helper function to add variables into bind parameters array in bulk
*
* @param Array Variable with values
*/
protected function _bindParams ($values) {
foreach ($values as $value)
$this->_bindParam ($value);
}
/**
* Helper function to add variables into bind parameters array and will return
* its SQL part of the query according to operator in ' $operator ?' or
* ' $operator ($subquery) ' formats
*
* @param Array Variable with values
*/
protected function _buildPair ($operator, $value) { protected function _buildPair ($operator, $value) {
if (!is_object($value)) { if (!is_object($value)) {
$this->_bindParam ($value); $this->_bindParam ($value);
$comparison = ' ' . $operator. ' ? '; return ' ' . $operator. ' ? ';
return $comparison;
} }
$subQuery = $value->getSubQuery(); $subQuery = $value->getSubQuery ();
$comparison = " " . $operator . " (" . $subQuery['query'] . ")"; $this->_bindParams ($subQuery['params']);
foreach ($subQuery['params'] as $v)
$this->_bindParam ($v);
return $comparison; return " " . $operator . " (" . $subQuery['query'] . ")";
} }
/** /**
...@@ -554,23 +571,97 @@ class MysqliDb ...@@ -554,23 +571,97 @@ class MysqliDb
*/ */
protected function _buildQuery($numRows = null, $tableData = null) protected function _buildQuery($numRows = null, $tableData = null)
{ {
$hasTableData = is_array($tableData); $this->_buildJoin();
$hasConditional = !empty($this->_where); $this->_buildTableData ($tableData);
$this->_buildWhere();
$this->_buildGroupBy();
$this->_buildOrderBy();
$this->_buildLimit ($numRows);
// Did the user call the "join" method? $this->_lastQuery = $this->replacePlaceHolders ($this->_query, $this->_bindParams);
if (!empty($this->_join)) {
foreach ($this->_join as $prop => $value) { if ($this->isSubQuery)
$this->_query .= " " . $prop . " on " . $value; return;
// Prepare query
$stmt = $this->_prepareQuery();
// Bind parameters to statement if any
if (count ($this->_bindParams) > 1)
call_user_func_array(array($stmt, 'bind_param'), $this->refValues($this->_bindParams));
return $stmt;
}
/**
* This helper method takes care of prepared statements' "bind_result method
* , when the number of variables to pass is unknown.
*
* @param mysqli_stmt $stmt Equal to the prepared statement object.
*
* @return array The results of the SQL fetch.
*/
protected function _dynamicBindResults(mysqli_stmt $stmt)
{
$parameters = array();
$results = array();
$meta = $stmt->result_metadata();
// if $meta is false yet sqlstate is true, there's no sql error but the query is
// most likely an update/insert/delete which doesn't produce any results
if(!$meta && $stmt->sqlstate) {
return array();
}
$row = array();
while ($field = $meta->fetch_field()) {
$row[$field->name] = null;
$parameters[] = & $row[$field->name];
}
// avoid out of memory bug in php 5.2 and 5.3
// https://github.com/joshcam/PHP-MySQLi-Database-Class/pull/119
if (version_compare (phpversion(), '5.4', '<'))
$stmt->store_result();
call_user_func_array(array($stmt, 'bind_result'), $parameters);
while ($stmt->fetch()) {
$x = array();
foreach ($row as $key => $val) {
$x[$key] = $val;
}
$this->count++;
array_push($results, $x);
}
return $results;
} }
/**
* Abstraction method that will build an JOIN part of the query
*/
protected function _buildJoin () {
if (empty ($this->_join))
return;
foreach ($this->_join as $prop => $value)
$this->_query .= " " . $prop . " on " . $value;
} }
// Determine INSERT or UPDATE query /**
if ($hasTableData) { * Abstraction method that will build an INSERT or UPDATE part of the query
*/
protected function _buildTableData ($tableData) {
if (!is_array ($tableData))
return;
$isInsert = strpos ($this->_query, 'INSERT'); $isInsert = strpos ($this->_query, 'INSERT');
$isUpdate = strpos ($this->_query, 'UPDATE'); $isUpdate = strpos ($this->_query, 'UPDATE');
if ($isInsert !== false) { if ($isInsert !== false) {
//is insert statement
$this->_query .= '(`' . implode(array_keys($tableData), '`, `') . '`)'; $this->_query .= '(`' . implode(array_keys($tableData), '`, `') . '`)';
$this->_query .= ' VALUES('; $this->_query .= ' VALUES(';
} }
...@@ -579,12 +670,20 @@ class MysqliDb ...@@ -579,12 +670,20 @@ class MysqliDb
if ($isUpdate !== false) if ($isUpdate !== false)
$this->_query .= "`" . $column . "` = "; $this->_query .= "`" . $column . "` = ";
// Subquery value
if (is_object ($value)) { if (is_object ($value)) {
$this->_query .= $this->_buildPair ("", $value) . ", "; $this->_query .= $this->_buildPair ("", $value) . ", ";
} else if (!is_array ($value)) { continue;
}
// Simple value
if (!is_array ($value)) {
$this->_bindParam ($value); $this->_bindParam ($value);
$this->_query .= '?, '; $this->_query .= '?, ';
} else { continue;
}
// Function value
$key = key ($value); $key = key ($value);
$val = $value[$key]; $val = $value[$key];
switch ($key) { switch ($key) {
...@@ -593,10 +692,8 @@ class MysqliDb ...@@ -593,10 +692,8 @@ class MysqliDb
break; break;
case '[F]': case '[F]':
$this->_query .= $val[0] . ", "; $this->_query .= $val[0] . ", ";
if (!empty ($val[1])) { if (!empty ($val[1]))
foreach ($val[1] as $v) $this->_bindParams ($val[1]);
$this->_bindParam ($v);
}
break; break;
case '[N]': case '[N]':
if ($val == null) if ($val == null)
...@@ -608,33 +705,41 @@ class MysqliDb ...@@ -608,33 +705,41 @@ class MysqliDb
die ("Wrong operation"); die ("Wrong operation");
} }
} }
}
$this->_query = rtrim($this->_query, ', '); $this->_query = rtrim($this->_query, ', ');
if ($isInsert !== false) if ($isInsert !== false)
$this->_query .= ')'; $this->_query .= ')';
} }
// Did the user call the "where" method? /**
if ($hasConditional) { * Abstraction method that will build the part of the WHERE conditions
*/
protected function _buildWhere () {
if (empty ($this->_where))
return;
//Prepair the where portion of the query //Prepair the where portion of the query
$this->_query .= ' WHERE '; $this->_query .= ' WHERE ';
$i = 0;
// Remove first AND/OR concatenator
$this->_where[0][0] = '';
foreach ($this->_where as $cond) { foreach ($this->_where as $cond) {
list ($concat, $wValue, $wKey) = $cond; list ($concat, $wValue, $wKey) = $cond;
// if its not a first condition insert its concatenator (AND or OR) $this->_query .= " " . $concat ." " . $wKey;
if ($i++ != 0)
$this->_query .= " $concat ";
$this->_query .= $wKey;
if (is_array ($wValue)) { // Empty value (raw where condition in wKey)
// if the value is an array, then this isn't a basic = comparison if ($wValue === null)
$key = key($wValue); continue;
// Simple = comparison
if (!is_array ($wValue))
$wValue = Array ('=' => $wValue);
$key = key ($wValue);
$val = $wValue[$key]; $val = $wValue[$key];
switch( strtolower($key) ) { switch (strtolower ($key)) {
case '0': case '0':
foreach ($wValue as $v) $this->_bindParams ($wValue);
$this->_bindParam ($v);
break; break;
case 'not in': case 'not in':
case 'in': case 'in':
...@@ -652,103 +757,58 @@ class MysqliDb ...@@ -652,103 +757,58 @@ class MysqliDb
case 'not between': case 'not between':
case 'between': case 'between':
$this->_query .= " $key ? AND ? "; $this->_query .= " $key ? AND ? ";
$this->_bindParam ($val[0]); $this->_bindParams ($val);
$this->_bindParam ($val[1]);
break; break;
default: default:
// We are using a comparison operator with only one parameter after it
$this->_query .= $this->_buildPair ($key, $val); $this->_query .= $this->_buildPair ($key, $val);
} }
} else if ($wValue === null) {
//
} else {
$this->_query .= $this->_buildPair ("=", $wValue);
}
} }
} }
// Did the user call the "groupBy" method? /**
if (!empty($this->_groupBy)) { * Abstraction method that will build the GROUP BY part of the WHERE statement
*
*/
protected function _buildGroupBy () {
if (empty ($this->_groupBy))
return;
$this->_query .= " GROUP BY "; $this->_query .= " GROUP BY ";
foreach ($this->_groupBy as $key => $value) { foreach ($this->_groupBy as $key => $value)
// prepares the reset of the SQL query.
$this->_query .= $value . ", "; $this->_query .= $value . ", ";
}
$this->_query = rtrim($this->_query, ', ') . " ";
}
// Did the user call the "orderBy" method?
if (!empty ($this->_orderBy)) {
$this->_query .= " ORDER BY ";
foreach ($this->_orderBy as $prop => $value) {
// prepares the reset of the SQL query.
$this->_query .= $prop . " " . $value . ", ";
}
$this->_query = rtrim ($this->_query, ', ') . " ";
}
// Did the user set a limit $this->_query = rtrim($this->_query, ', ') . " ";
if (isset($numRows)) {
if (is_array ($numRows))
$this->_query .= ' LIMIT ' . (int)$numRows[0] . ', ' . (int)$numRows[1];
else
$this->_query .= ' LIMIT ' . (int)$numRows;
} }
$this->_lastQuery = $this->replacePlaceHolders($this->_query, $this->_bindParams); /**
* Abstraction method that will build the LIMIT part of the WHERE statement
if ($this->isSubQuery) *
* @param int $numRows The number of rows total to return.
*/
protected function _buildOrderBy () {
if (empty ($this->_orderBy))
return; return;
// Prepare query $this->_query .= " ORDER BY ";
$stmt = $this->_prepareQuery(); foreach ($this->_orderBy as $prop => $value)
$this->_query .= $prop . " " . $value . ", ";
// Bind parameters to statement if any
if (count ($this->_bindParams) > 1)
call_user_func_array(array($stmt, 'bind_param'), $this->refValues($this->_bindParams));
return $stmt; $this->_query = rtrim ($this->_query, ', ') . " ";
} }
/** /**
* This helper method takes care of prepared statements' "bind_result method * Abstraction method that will build the LIMIT part of the WHERE statement
* , when the number of variables to pass is unknown.
*
* @param mysqli_stmt $stmt Equal to the prepared statement object.
* *
* @return array The results of the SQL fetch. * @param int $numRows The number of rows total to return.
*/ */
protected function _dynamicBindResults(mysqli_stmt $stmt) protected function _buildLimit ($numRows) {
{ if (!isset ($numRows))
$parameters = array(); return;
$results = array();
$meta = $stmt->result_metadata();
// if $meta is false yet sqlstate is true, there's no sql error but the query is
// most likely an update/insert/delete which doesn't produce any results
if(!$meta && $stmt->sqlstate) {
return array();
}
$row = array();
while ($field = $meta->fetch_field()) {
$row[$field->name] = null;
$parameters[] = & $row[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $parameters);
while ($stmt->fetch()) {
$x = array();
foreach ($row as $key => $val) {
$x[$key] = $val;
}
$this->count++;
array_push($results, $x);
}
return $results; if (is_array ($numRows))
$this->_query .= ' LIMIT ' . (int)$numRows[0] . ', ' . (int)$numRows[1];
else
$this->_query .= ' LIMIT ' . (int)$numRows;
} }
/** /**
......
...@@ -68,6 +68,9 @@ $data = Array( ...@@ -68,6 +68,9 @@ $data = Array(
$id = $db->insert ('users', $data); $id = $db->insert ('users', $data);
if ($id) if ($id)
echo 'user was created. Id=' . $id; echo 'user was created. Id=' . $id;
else
echo 'insert failed: ' . $db->getLastError();
``` ```
### Update Query ### Update Query
...@@ -81,7 +84,10 @@ $data = Array ( ...@@ -81,7 +84,10 @@ $data = Array (
// active = !active; // active = !active;
); );
$db->where ('id', 1); $db->where ('id', 1);
if($db->update ('users', $data)) echo 'successfully updated'; if ($db->update ('users', $data))
echo $db->count . ' records were updated';
else
echo 'update failed: ' . $db->getLastError();
``` ```
### Select Query ### Select Query
...@@ -121,14 +127,20 @@ if($db->delete('users')) echo 'successfully deleted'; ...@@ -121,14 +127,20 @@ if($db->delete('users')) echo 'successfully deleted';
``` ```
### Generic Query Method ### Generic Query Method
By default rawQuery() will filter out special characters so if you getting problems with it
you might try to disable filtering function. In this case make sure that all external variables are passed to the query via bind variables
```php ```php
$users = $db->rawQuery('SELECT * from users'); // filtering enabled
$users = $db->rawQuery('SELECT * from users where customerId=?', Array (10));
// filtering disabled
//$users = $db->rawQuery('SELECT * from users where id >= ?', Array (10), false);
foreach ($users as $user) { foreach ($users as $user) {
print_r ($user); print_r ($user);
} }
``` ```
### Raw Query Method More advanced examples:
```php ```php
$params = Array(1, 'admin'); $params = Array(1, 'admin');
$users = $db->rawQuery("SELECT id, firstName, lastName FROM users WHERE id = ? AND login = ?", $params); $users = $db->rawQuery("SELECT id, firstName, lastName FROM users WHERE id = ? AND login = ?", $params);
...@@ -136,8 +148,17 @@ print_r($users); // contains Array of returned rows ...@@ -136,8 +148,17 @@ print_r($users); // contains Array of returned rows
// will handle any SQL query // will handle any SQL query
$params = Array(10, 1, 10, 11, 2, 10); $params = Array(10, 1, 10, 11, 2, 10);
$resutls = $db->rawQuery("(SELECT a FROM t1 WHERE a = ? AND B = ? ORDER BY a LIMIT ?) UNION(SELECT a FROM t2 WHERE a = ? AND B = ? ORDER BY a LIMIT ?)", $params); $q = "(
print_r($results); // contains Array of returned rows SELECT a FROM t1
WHERE a = ? AND B = ?
ORDER BY a LIMIT ?
) UNION (
SELECT a FROM t2
WHERE a = ? AND B = ?
ORDER BY a LIMIT ?
)";
$resutls = $db->rawQuery ($q, $params);
print_r ($results); // contains Array of returned rows
``` ```
......
...@@ -125,6 +125,10 @@ if ($db->count != 1) { ...@@ -125,6 +125,10 @@ if ($db->count != 1) {
$db->where ("active", false); $db->where ("active", false);
$db->update("users", Array ("active" => $db->not())); $db->update("users", Array ("active" => $db->not()));
if ($db->count != 2) {
echo "Invalid update count with not()";
exit;
}
$db->where ("active", true); $db->where ("active", true);
$users = $db->get("users"); $users = $db->get("users");
...@@ -133,12 +137,19 @@ if ($db->count != 3) { ...@@ -133,12 +137,19 @@ if ($db->count != 3) {
exit; exit;
} }
$db->where ("active", true);
$users = $db->get("users", 2);
if ($db->count != 2) {
echo "Invalid total insert count with boolean";
exit;
}
// TODO // TODO
//$db->where("createdAt", Array (">" => $db->interval("-1h"))); //$db->where("createdAt", Array (">" => $db->interval("-1h")));
//$users = $db->get("users"); //$users = $db->get("users");
//print_r ($users); //print_r ($users);
$db->where("firstname", '%John%', 'LIKE'); $db->where("firstname", Array ('LIKE' => '%John%'));
$users = $db->get("users"); $users = $db->get("users");
if ($db->count != 1) { if ($db->count != 1) {
echo "Invalid insert count in LIKE: ".$db->count; echo "Invalid insert count in LIKE: ".$db->count;
...@@ -160,6 +171,11 @@ $upData = Array ( ...@@ -160,6 +171,11 @@ $upData = Array (
); );
$db->where ("id", 1); $db->where ("id", 1);
$cnt = $db->update("users", $upData); $cnt = $db->update("users", $upData);
if ($db->count != 1) {
echo "Invalid update count with functions";
exit;
}
$db->where ("id", 1); $db->where ("id", 1);
$r = $db->getOne("users"); $r = $db->getOne("users");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment