From 3874194928cdd5368fc59911539a973edf0c2a0d Mon Sep 17 00:00:00 2001 From: Alexander Butenko Date: Tue, 8 Jul 2014 14:01:37 +0300 Subject: [PATCH 1/9] Avoid out of memory bug in php 5.2 and 5.3 --- MysqliDb.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MysqliDb.php b/MysqliDb.php index 7c8e57e5..bfc0ddd2 100644 --- a/MysqliDb.php +++ b/MysqliDb.php @@ -737,6 +737,12 @@ protected function _dynamicBindResults(mysqli_stmt $stmt) $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()) { From 8f15313ec6812fa6d923e7f8e864d496fb7f0064 Mon Sep 17 00:00:00 2001 From: Alexander Butenko Date: Tue, 8 Jul 2014 15:15:09 +0300 Subject: [PATCH 2/9] clearify readme on update() function --- readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 0d4a5256..82d9b578 100644 --- a/readme.md +++ b/readme.md @@ -81,8 +81,11 @@ $data = Array ( // active = !active; ); $db->where ('id', 1); -if($db->update ('users', $data)) echo 'successfully updated'; +$cols = $db->update ('users', $data); +echo $cols . ' records were updated'; ``` +Note that update query will return 0 in case update query will fail AND also in case +where no records were modified. ### Select Query After any select/get function calls amount or returned rows From fe6728ffac97d2c194e08f8dd400c717f034e19d Mon Sep 17 00:00:00 2001 From: Alexander Butenko Date: Tue, 8 Jul 2014 15:20:53 +0300 Subject: [PATCH 3/9] update () should return -1 in case if failed --- MysqliDb.php | 8 +++++--- readme.md | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/MysqliDb.php b/MysqliDb.php index bfc0ddd2..1a34c741 100644 --- a/MysqliDb.php +++ b/MysqliDb.php @@ -317,8 +317,11 @@ public function update($tableName, $tableData) $this->_query = "UPDATE " . self::$_prefix . $tableName ." SET "; $stmt = $this->_buildQuery(null, $tableData); - $stmt->execute(); - $this->_stmtError = $stmt->error; + if ($stmt->execute() == false) { + $this->reset(); + $this->_stmtError = $stmt->error; + return -1; + } $this->reset(); return ($stmt->affected_rows > 0); @@ -741,7 +744,6 @@ protected function _dynamicBindResults(mysqli_stmt $stmt) // 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); diff --git a/readme.md b/readme.md index 82d9b578..721f51c1 100644 --- a/readme.md +++ b/readme.md @@ -82,7 +82,10 @@ $data = Array ( ); $db->where ('id', 1); $cols = $db->update ('users', $data); -echo $cols . ' records were updated'; +if ($cols == -1) + echo 'update failed: ' . $db->getLastError(); +else + echo $cols . ' records were updated'; ``` Note that update query will return 0 in case update query will fail AND also in case where no records were modified. From 94fea951af0b190a0a718f50b167cebab8b91580 Mon Sep 17 00:00:00 2001 From: Alexander Butenko Date: Tue, 8 Jul 2014 15:27:44 +0300 Subject: [PATCH 4/9] bug #129: Check correct execution status instead of affected rows, --- MysqliDb.php | 7 ++++--- readme.md | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/MysqliDb.php b/MysqliDb.php index 1a34c741..357ee369 100644 --- a/MysqliDb.php +++ b/MysqliDb.php @@ -316,15 +316,16 @@ public function update($tableName, $tableData) $this->_query = "UPDATE " . self::$_prefix . $tableName ." SET "; - $stmt = $this->_buildQuery(null, $tableData); + $stmt = $this->_buildQuery (null, $tableData); if ($stmt->execute() == false) { $this->reset(); $this->_stmtError = $stmt->error; - return -1; + return false; } $this->reset(); + $this->count = $stmt->affected_rows; - return ($stmt->affected_rows > 0); + return true; } /** diff --git a/readme.md b/readme.md index 721f51c1..1be7091e 100644 --- a/readme.md +++ b/readme.md @@ -68,6 +68,9 @@ $data = Array( $id = $db->insert ('users', $data); if ($id) echo 'user was created. Id=' . $id; +else + echo 'insert failed: ' . $db->getLastError(); + ``` ### Update Query @@ -81,14 +84,11 @@ $data = Array ( // active = !active; ); $db->where ('id', 1); -$cols = $db->update ('users', $data); -if ($cols == -1) - echo 'update failed: ' . $db->getLastError(); +if ($db->update ('users', $data)) + echo $db->count . ' records were updated'; else - echo $cols . ' records were updated'; + echo 'update failed: ' . $db->getLastError(); ``` -Note that update query will return 0 in case update query will fail AND also in case -where no records were modified. ### Select Query After any select/get function calls amount or returned rows From 2c0761bc071b1580719fda83ead96f8eaf6df862 Mon Sep 17 00:00:00 2001 From: Alexander Butenko Date: Tue, 8 Jul 2014 15:31:07 +0300 Subject: [PATCH 5/9] Refactor bugfix to a more streamline code --- MysqliDb.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/MysqliDb.php b/MysqliDb.php index 357ee369..a2e9b3d7 100644 --- a/MysqliDb.php +++ b/MysqliDb.php @@ -317,15 +317,12 @@ public function update($tableName, $tableData) $this->_query = "UPDATE " . self::$_prefix . $tableName ." SET "; $stmt = $this->_buildQuery (null, $tableData); - if ($stmt->execute() == false) { - $this->reset(); - $this->_stmtError = $stmt->error; - return false; - } + $status = $stmt->execute(); $this->reset(); + $this->_stmtError = $stmt->error; $this->count = $stmt->affected_rows; - return true; + return $status; } /** From 2cffff2d1c0e8c19fffb04b421f6656b34ccb4f1 Mon Sep 17 00:00:00 2001 From: Alexander Butenko Date: Tue, 8 Jul 2014 20:26:24 +0300 Subject: [PATCH 6/9] Utilize new count data on update in tests --- tests.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests.php b/tests.php index f6590186..66107cd3 100644 --- a/tests.php +++ b/tests.php @@ -125,6 +125,10 @@ function createTable ($name, $data) { $db->where ("active", false); $db->update("users", Array ("active" => $db->not())); +if ($db->count != 2) { + echo "Invalid update count with not()"; + exit; +} $db->where ("active", true); $users = $db->get("users"); @@ -160,6 +164,11 @@ function createTable ($name, $data) { ); $db->where ("id", 1); $cnt = $db->update("users", $upData); +if ($db->count != 1) { + echo "Invalid update count with functions"; + exit; +} + $db->where ("id", 1); $r = $db->getOne("users"); From 5cf475391bcc59eeb99e25c85f32d6ecb22dfd00 Mon Sep 17 00:00:00 2001 From: Alexander Butenko Date: Wed, 9 Jul 2014 20:16:46 +0300 Subject: [PATCH 7/9] buildWhere() refactoring --- MysqliDb.php | 351 +++++++++++++++++++++++++++++---------------------- tests.php | 9 +- 2 files changed, 209 insertions(+), 151 deletions(-) diff --git a/MysqliDb.php b/MysqliDb.php index a2e9b3d7..0125b74b 100644 --- a/MysqliDb.php +++ b/MysqliDb.php @@ -522,25 +522,38 @@ protected function _determineType($item) * * @param string Variable value */ - protected function _bindParam($value) - { + protected function _bindParam($value) { $this->_bindParams[0] .= $this->_determineType ($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) { if (!is_object($value)) { $this->_bindParam ($value); - $comparison = ' ' . $operator. ' ? '; - return $comparison; + return ' ' . $operator. ' ? '; } - $subQuery = $value->getSubQuery(); - $comparison = " " . $operator . " (" . $subQuery['query'] . ")"; - foreach ($subQuery['params'] as $v) - $this->_bindParam ($v); + $subQuery = $value->getSubQuery (); + $this->_bindParams ($subQuery['params']); - return $comparison; + return " " . $operator . " (" . $subQuery['query'] . ")"; } /** @@ -555,148 +568,14 @@ protected function _buildPair ($operator, $value) { */ protected function _buildQuery($numRows = null, $tableData = null) { - $hasTableData = is_array($tableData); - $hasConditional = !empty($this->_where); - - // Did the user call the "join" method? - if (!empty($this->_join)) { - foreach ($this->_join as $prop => $value) { - $this->_query .= " " . $prop . " on " . $value; - } - } + $this->_buildJoin(); + $this->_buildTableData ($tableData); + $this->_buildWhere(); + $this->_buildGroupBy(); + $this->_buildOrderBy(); + $this->_buildLimit ($numRows); - // Determine INSERT or UPDATE query - if ($hasTableData) { - $isInsert = strpos ($this->_query, 'INSERT'); - $isUpdate = strpos ($this->_query, 'UPDATE'); - - if ($isInsert !== false) { - //is insert statement - $this->_query .= '(`' . implode(array_keys($tableData), '`, `') . '`)'; - $this->_query .= ' VALUES('; - } - - foreach ($tableData as $column => $value) { - if ($isUpdate !== false) - $this->_query .= "`" . $column . "` = "; - - if (is_object ($value)) { - $this->_query .= $this->_buildPair ("", $value) . ", "; - } else if (!is_array ($value)) { - $this->_bindParam ($value); - $this->_query .= '?, '; - } else { - $key = key ($value); - $val = $value[$key]; - switch ($key) { - case '[I]': - $this->_query .= $column . $val . ", "; - break; - case '[F]': - $this->_query .= $val[0] . ", "; - if (!empty ($val[1])) { - foreach ($val[1] as $v) - $this->_bindParam ($v); - } - break; - case '[N]': - if ($val == null) - $this->_query .= "!" . $column . ", "; - else - $this->_query .= "!" . $val . ", "; - break; - default: - die ("Wrong operation"); - } - } - } - $this->_query = rtrim($this->_query, ', '); - if ($isInsert !== false) - $this->_query .= ')'; - } - - // Did the user call the "where" method? - if ($hasConditional) { - //Prepair the where portion of the query - $this->_query .= ' WHERE '; - $i = 0; - foreach ($this->_where as $cond) { - list ($concat, $wValue, $wKey) = $cond; - - // if its not a first condition insert its concatenator (AND or OR) - if ($i++ != 0) - $this->_query .= " $concat "; - $this->_query .= $wKey; - - if (is_array ($wValue)) { - // if the value is an array, then this isn't a basic = comparison - $key = key($wValue); - $val = $wValue[$key]; - switch( strtolower($key) ) { - case '0': - foreach ($wValue as $v) - $this->_bindParam ($v); - break; - case 'not in': - case 'in': - $comparison = ' ' . $key . ' ('; - if (is_object ($val)) { - $comparison .= $this->_buildPair ("", $val); - } else { - foreach ($val as $v) { - $comparison .= ' ?,'; - $this->_bindParam ($v); - } - } - $this->_query .= rtrim($comparison, ',').' ) '; - break; - case 'not between': - case 'between': - $this->_query .= " $key ? AND ? "; - $this->_bindParam ($val[0]); - $this->_bindParam ($val[1]); - break; - default: - // We are using a comparison operator with only one parameter after it - $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)) { - $this->_query .= " GROUP BY "; - foreach ($this->_groupBy as $key => $value) { - // prepares the reset of the SQL query. - $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 - 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); + $this->_lastQuery = $this->replacePlaceHolders ($this->_query, $this->_bindParams); if ($this->isSubQuery) return; @@ -757,6 +636,178 @@ protected function _dynamicBindResults(mysqli_stmt $stmt) 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; + } + + /** + * 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'); + $isUpdate = strpos ($this->_query, 'UPDATE'); + + if ($isInsert !== false) { + $this->_query .= '(`' . implode(array_keys($tableData), '`, `') . '`)'; + $this->_query .= ' VALUES('; + } + + foreach ($tableData as $column => $value) { + if ($isUpdate !== false) + $this->_query .= "`" . $column . "` = "; + + // Subquery value + if (is_object ($value)) { + $this->_query .= $this->_buildPair ("", $value) . ", "; + continue; + } + + // Simple value + if (!is_array ($value)) { + $this->_bindParam ($value); + $this->_query .= '?, '; + continue; + } + + // Function value + $key = key ($value); + $val = $value[$key]; + switch ($key) { + case '[I]': + $this->_query .= $column . $val . ", "; + break; + case '[F]': + $this->_query .= $val[0] . ", "; + if (!empty ($val[1])) + $this->_bindParams ($val[1]); + break; + case '[N]': + if ($val == null) + $this->_query .= "!" . $column . ", "; + else + $this->_query .= "!" . $val . ", "; + break; + default: + die ("Wrong operation"); + } + } + $this->_query = rtrim($this->_query, ', '); + if ($isInsert !== false) + $this->_query .= ')'; + } + + /** + * 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 + $this->_query .= ' WHERE '; + + // Remove first AND/OR concatenator + $this->_where[0][0] = ''; + foreach ($this->_where as $cond) { + list ($concat, $wValue, $wKey) = $cond; + + $this->_query .= " " . $concat ." " . $wKey; + + // Empty value (raw where condition in wKey) + if ($wValue === null) + continue; + + // Simple = comparison + if (!is_array ($wValue)) + $wValue = Array ('=' => $wValue); + + $key = key ($wValue); + $val = $wValue[$key]; + switch (strtolower ($key)) { + case '0': + $this->_bindParams ($wValue); + break; + case 'not in': + case 'in': + $comparison = ' ' . $key . ' ('; + if (is_object ($val)) { + $comparison .= $this->_buildPair ("", $val); + } else { + foreach ($val as $v) { + $comparison .= ' ?,'; + $this->_bindParam ($v); + } + } + $this->_query .= rtrim($comparison, ',').' ) '; + break; + case 'not between': + case 'between': + $this->_query .= " $key ? AND ? "; + $this->_bindParams ($val); + break; + default: + $this->_query .= $this->_buildPair ($key, $val); + } + } + } + + /** + * 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 "; + foreach ($this->_groupBy as $key => $value) + $this->_query .= $value . ", "; + + $this->_query = rtrim($this->_query, ', ') . " "; + } + + /** + * Abstraction method that will build the LIMIT part of the WHERE statement + * + * @param int $numRows The number of rows total to return. + */ + protected function _buildOrderBy () { + if (empty ($this->_orderBy)) + return; + + $this->_query .= " ORDER BY "; + foreach ($this->_orderBy as $prop => $value) + $this->_query .= $prop . " " . $value . ", "; + + $this->_query = rtrim ($this->_query, ', ') . " "; + } + + /** + * Abstraction method that will build the LIMIT part of the WHERE statement + * + * @param int $numRows The number of rows total to return. + */ + protected function _buildLimit ($numRows) { + if (!isset ($numRows)) + return; + + if (is_array ($numRows)) + $this->_query .= ' LIMIT ' . (int)$numRows[0] . ', ' . (int)$numRows[1]; + else + $this->_query .= ' LIMIT ' . (int)$numRows; + } + /** * Method attempts to prepare the SQL query * and throws an error if there was a problem. diff --git a/tests.php b/tests.php index 66107cd3..e57a561a 100644 --- a/tests.php +++ b/tests.php @@ -137,12 +137,19 @@ function createTable ($name, $data) { exit; } +$db->where ("active", true); +$users = $db->get("users", 2); +if ($db->count != 2) { + echo "Invalid total insert count with boolean"; + exit; +} + // TODO //$db->where("createdAt", Array (">" => $db->interval("-1h"))); //$users = $db->get("users"); //print_r ($users); -$db->where("firstname", '%John%', 'LIKE'); +$db->where("firstname", Array ('LIKE' => '%John%')); $users = $db->get("users"); if ($db->count != 1) { echo "Invalid insert count in LIKE: ".$db->count; From 89c80f1c91492d52880806475954f4e5a89c76f7 Mon Sep 17 00:00:00 2001 From: Alexander Butenko Date: Sat, 12 Jul 2014 17:17:58 +0300 Subject: [PATCH 8/9] Added param to rawQuery() to disable query variable filtering --- MysqliDb.php | 7 +++++-- readme.md | 8 +++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/MysqliDb.php b/MysqliDb.php index 0125b74b..2d9c5857 100644 --- a/MysqliDb.php +++ b/MysqliDb.php @@ -188,12 +188,15 @@ public function setPrefix($prefix = '') * * @param string $query Contains a user-provided query. * @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. */ - public function rawQuery($query, $bindParams = null) + public function rawQuery ($query, $bindParams = null, $sanitize = true) { - $this->_query = filter_var ($query, FILTER_SANITIZE_STRING, + $this->_query = $query; + if ($sanitize) + $this->_query = filter_var ($query, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES); $stmt = $this->_prepareQuery(); diff --git a/readme.md b/readme.md index 1be7091e..033286d9 100644 --- a/readme.md +++ b/readme.md @@ -127,8 +127,14 @@ if($db->delete('users')) echo 'successfully deleted'; ``` ### 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 -$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) { print_r ($user); } From 5ef4553e0504772117144774d74c6122903185bf Mon Sep 17 00:00:00 2001 From: Alexander Butenko Date: Sat, 12 Jul 2014 17:23:31 +0300 Subject: [PATCH 9/9] Update readme a bit --- readme.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 033286d9..90c1d48e 100644 --- a/readme.md +++ b/readme.md @@ -140,7 +140,7 @@ foreach ($users as $user) { } ``` -### Raw Query Method +More advanced examples: ```php $params = Array(1, 'admin'); $users = $db->rawQuery("SELECT id, firstName, lastName FROM users WHERE id = ? AND login = ?", $params); @@ -148,8 +148,17 @@ print_r($users); // contains Array of returned rows // will handle any SQL query $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); -print_r($results); // contains Array of returned rows +$q = "( + 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 ```