1 changed files with 227 additions and 1 deletions
@ -1,4 +1,63 @@
|
||||
<?php |
||||
/** |
||||
* @author [email protected] |
||||
* @link http://phpfaq.ru/safemysql |
||||
* |
||||
* Safe and convenient vay to handle SQL queries utilizing type-hinted placeholders. |
||||
* |
||||
* Key features |
||||
* - set of helper functions to get the desired result right out of query, like in PEAR::DB |
||||
* - conditional query building using parse() method to build queries of whatever comlexity, |
||||
* while keeping extra safety of placeholders |
||||
* - type-hinted placeholders |
||||
* |
||||
* Type-hinted placeholders are great because |
||||
* - safe, as any other [properly implemented] placeholders |
||||
* - no need for manual escaping or binding, makes the code extra DRY |
||||
* - allows support for non-standard types such as identifier or array, which saves A LOT of pain in the back. |
||||
* |
||||
* Supported placeholders at the moment are: |
||||
* |
||||
* ?s ("string") - strings (also DATE, FLOAT and DECIMAL) |
||||
* ?i ("integer") - the name says it all |
||||
* ?n ("name") - identifiers (table and field names) |
||||
* ?a ("array") - complex placeholder for IN() operator (substituted with string of 'a','b','c' format, without parentesis) |
||||
* ?u ("update") - complex placeholder for SET operator (substituted with string of `field`='value',`field`='value' format) |
||||
* and |
||||
* ?p ("parsed") - special type placeholder, for inserting already parsed statements without any processing, to avoid double parsing. |
||||
* |
||||
* Some examples: |
||||
* |
||||
* $db = new SafeMySQL(); // with default settings |
||||
* |
||||
* $opts = array( |
||||
* 'user' => 'user', |
||||
* 'pass' => 'pass', |
||||
* 'db' => 'db', |
||||
* 'charset' => 'latin1' |
||||
* ); |
||||
* $db = new SafeMySQL($opts); // with some of the default settings overwritten |
||||
* |
||||
* |
||||
* $name = $db->getOne('SELECT name FROM table WHERE id = ?i',$_GET['id']); |
||||
* $data = $db->getInd('id','SELECT * FROM ?n WHERE id IN ?a','table', array(1,2)); |
||||
* $data = $db->getAll("SELECT * FROM ?n WHERE mod=?s LIMIT ?i",$table,$mod,$limit); |
||||
|
||||
* $ids = $db->getCol("SELECT id FROM tags WHERE tagname = ?s",$tag); |
||||
* $data = $db->getAll("SELECT * FROM table WHERE category IN (?a)",$ids); |
||||
* |
||||
* $data = array('offers_in' => $in, 'offers_out' => $out); |
||||
* $sql = "INSERT INTO stats SET pid=?i,dt=CURDATE(),?u ON DUPLICATE KEY UPDATE ?u"; |
||||
* $db->query($sql,$pid,$data,$data); |
||||
* |
||||
* if ($var === NULL) { |
||||
* $sqlpart = "field is NULL"; |
||||
* } else { |
||||
* $sqlpart = $db->parse("field = ?s", $var); |
||||
* } |
||||
* $data = $db->getAll("SELECT * FROM table WHERE ?p", $bar, $sqlpart); |
||||
* |
||||
*/ |
||||
|
||||
class SafeMySQL |
||||
{ |
||||
@ -46,36 +105,83 @@ class SafeMySQL
|
||||
unset($opt); // I am paranoid |
||||
} |
||||
|
||||
/** |
||||
* Conventional function to run a query with placeholders. A mysqli_query wrapper with placeholders support |
||||
* |
||||
* Examples: |
||||
* $db->query("DELETE FROM table WHERE id=?i", $id); |
||||
* |
||||
* @param string $query - an SQL query with placeholders |
||||
* @param mixed $arg,... unlimited number of arguments to match placeholders in the query |
||||
* @return resource|FALSE whatever mysqli_query returns |
||||
*/ |
||||
public function query() |
||||
{ |
||||
return $this->rawQuery($this->prepareQuery(func_get_args())); |
||||
} |
||||
|
||||
/** |
||||
* Conventional function to fetch single row. |
||||
* |
||||
* @param resource $result - myqli result |
||||
* @param int $mode - optional fetch mode, RESULT_ASSOC|RESULT_NUM, default RESULT_ASSOC |
||||
* @return array|FALSE whatever mysqli_fetch_array returns |
||||
*/ |
||||
public function fetch($result,$mode=self::RESULT_ASSOC) |
||||
{ |
||||
return mysqli_fetch_array($result, $mode); |
||||
} |
||||
|
||||
/** |
||||
* Conventional function to get number of affected rows. |
||||
* |
||||
* @return int whatever mysqli_affected_rows returns |
||||
*/ |
||||
public function affectedRows() |
||||
{ |
||||
return mysqli_affected_rows ($this->conn); |
||||
} |
||||
|
||||
/** |
||||
* Conventional function to get last insert id. |
||||
* |
||||
* @return int whatever mysqli_insert_id returns |
||||
*/ |
||||
public function insertId() |
||||
{ |
||||
return mysqli_insert_id($this->conn); |
||||
} |
||||
|
||||
/** |
||||
* Conventional function to get number of rows in the resultset. |
||||
* |
||||
* @param resource $result - myqli result |
||||
* @return int whatever mysqli_num_rows returns |
||||
*/ |
||||
public function numRows($result) |
||||
{ |
||||
return mysqli_num_rows($result); |
||||
} |
||||
|
||||
/** |
||||
* Conventional function to free the resultset. |
||||
*/ |
||||
public function free($result) |
||||
{ |
||||
mysqli_free_result($result); |
||||
} |
||||
|
||||
/** |
||||
* Helper function to get scalar value right out of query and optional arguments |
||||
* |
||||
* Examples: |
||||
* $name = $db->getOne("SELECT name FROM table WHERE id=1"); |
||||
* $name = $db->getOne("SELECT name FROM table WHERE id=?i", $id); |
||||
* |
||||
* @param string $query - an SQL query with placeholders |
||||
* @param mixed $arg,... unlimited number of arguments to match placeholders in the query |
||||
* @return string|FALSE either first column of the first row of resultset or FALSE if none found |
||||
*/ |
||||
public function getOne() |
||||
{ |
||||
$query = $this->prepareQuery(func_get_args()); |
||||
@ -90,6 +196,17 @@ class SafeMySQL
|
||||
return FALSE; |
||||
} |
||||
|
||||
/** |
||||
* Helper function to get single row right out of query and optional arguments |
||||
* |
||||
* Examples: |
||||
* $data = $db->getRow("SELECT * FROM table WHERE id=1"); |
||||
* $data = $db->getOne("SELECT * FROM table WHERE id=?i", $id); |
||||
* |
||||
* @param string $query - an SQL query with placeholders |
||||
* @param mixed $arg,... unlimited number of arguments to match placeholders in the query |
||||
* @return array|FALSE either associative array contains first row of resultset or FALSE if none found |
||||
*/ |
||||
public function getRow() |
||||
{ |
||||
$query = $this->prepareQuery(func_get_args()); |
||||
@ -101,6 +218,17 @@ class SafeMySQL
|
||||
return FALSE; |
||||
} |
||||
|
||||
/** |
||||
* Helper function to get single column right out of query and optional arguments |
||||
* |
||||
* Examples: |
||||
* $ids = $db->getCol("SELECT id FROM table WHERE cat=1"); |
||||
* $ids = $db->getCol("SELECT id FROM tags WHERE tagname = ?s", $tag); |
||||
* |
||||
* @param string $query - an SQL query with placeholders |
||||
* @param mixed $arg,... unlimited number of arguments to match placeholders in the query |
||||
* @return array|FALSE either enumerated array of first fields of all rows of resultset or FALSE if none found |
||||
*/ |
||||
public function getCol() |
||||
{ |
||||
$ret = array(); |
||||
@ -116,6 +244,17 @@ class SafeMySQL
|
||||
return $ret; |
||||
} |
||||
|
||||
/** |
||||
* Helper function to get all the rows of resultset right out of query and optional arguments |
||||
* |
||||
* Examples: |
||||
* $data = $db->getAll("SELECT * FROM table"); |
||||
* $data = $db->getAll("SELECT * FROM table LIMIT ?i,?i", $start, $rows); |
||||
* |
||||
* @param string $query - an SQL query with placeholders |
||||
* @param mixed $arg,... unlimited number of arguments to match placeholders in the query |
||||
* @return array enumerated 2d array contains the resultset. Empty if no rows found. |
||||
*/ |
||||
public function getAll() |
||||
{ |
||||
$ret = array(); |
||||
@ -131,6 +270,18 @@ class SafeMySQL
|
||||
return $ret; |
||||
} |
||||
|
||||
/** |
||||
* Helper function to get all the rows of resultset into indexed array right out of query and optional arguments |
||||
* |
||||
* Examples: |
||||
* $data = $db->getInd("id", "SELECT * FROM table"); |
||||
* $data = $db->getInd("id", "SELECT * FROM table LIMIT ?i,?i", $start, $rows); |
||||
* |
||||
* @param string $index - name of the field which value is used to index resulting array |
||||
* @param string $query - an SQL query with placeholders |
||||
* @param mixed $arg,... unlimited number of arguments to match placeholders in the query |
||||
* @return array - associative 2d array contains the resultset. Empty if no rows found. |
||||
*/ |
||||
public function getInd() |
||||
{ |
||||
$args = func_get_args(); |
||||
@ -149,6 +300,17 @@ class SafeMySQL
|
||||
return $ret; |
||||
} |
||||
|
||||
/** |
||||
* Helper function to get a dictionary-style array right out of query and optional arguments |
||||
* |
||||
* Examples: |
||||
* $data = $db->getIndCol("name", "SELECT name, id FROM cities"); |
||||
* |
||||
* @param string $index - name of the field which value is used to index resulting array |
||||
* @param string $query - an SQL query with placeholders |
||||
* @param mixed $arg,... unlimited number of arguments to match placeholders in the query |
||||
* @return array - associative array contains key=value pairs out of resultset. Empty if no rows found. |
||||
*/ |
||||
public function getIndCol() |
||||
{ |
||||
$args = func_get_args(); |
||||
@ -169,17 +331,70 @@ class SafeMySQL
|
||||
return $ret; |
||||
} |
||||
|
||||
/** |
||||
* Function to parse placeholders either in the full query or in query part |
||||
* useful for debug |
||||
* and conditional query building |
||||
* |
||||
* Examples: |
||||
* $query = $db->parse("SELECT * FROM table WHERE foo=?s AND bar=?s", $foo, $bar); |
||||
* echo $query; |
||||
* |
||||
* if ($foo) { |
||||
* $qpart = $db->parse(" AND foo=?s", $foo); |
||||
* } |
||||
* $data = $db->getAll("SELECT * FROM table WHERE bar=?s ?p", $bar, $qpart); |
||||
* |
||||
* @param string $query - whatever expression contains placeholders |
||||
* @param mixed $arg,... unlimited number of arguments to match placeholders in the expression |
||||
* @return string - initial expression with placeholders substituted with data. |
||||
*/ |
||||
public function parse() |
||||
{ |
||||
return $this->prepareQuery(func_get_args()); |
||||
} |
||||
|
||||
/** |
||||
* function to implement whitelisting feature |
||||
* sometimes we can't allow a non-validated user-supplied data to the query even through placeholder |
||||
* especially if it comes down to SQL OPERATORS |
||||
* |
||||
* Example: |
||||
* |
||||
* $order = $db->whiteList($_GET['order'], array('name','price')); |
||||
* $dir = $db->whiteList($_GET['dir'], array('ASC','DESC')); |
||||
* if (!$order || !dir) { |
||||
* throw new http404(); //non-expected values should cause 404 or similar response |
||||
* } |
||||
* $sql = "SELECT * FROM table ORDER BY ?p ?p LIMIT ?i,?i" |
||||
* $data = $db->getArr($sql, $order, $dir, $start, $per_page); |
||||
* |
||||
* @param string $iinput - field name to test |
||||
* @param array $allowed - an array with allowed variants |
||||
* @param string $default - optional variable to set if no match found. Default to false. |
||||
* @return string|FALSE - either sanitized value or FALSE |
||||
*/ |
||||
public function whiteList($input,$allowed,$default=FALSE) |
||||
{ |
||||
$found = array_search($input,$allowed); |
||||
return ($found === FALSE) ? $default : $allowed[$found]; |
||||
} |
||||
|
||||
/** |
||||
* function to filter out arrays, for the whitelisting purposes |
||||
* useful to pass entire superglobal to the INSERT or UPDATE query |
||||
* OUGHT to be used for this purpose, as there could be fuelds whic user inallowed to alter. |
||||
* |
||||
* Example: |
||||
* $allowed = array('title','url','body','rating','term','type'); |
||||
* $data = $db->filterArray($_POST,$allowed); |
||||
* $sql = "INSERT INTO ?n SET ?u"; |
||||
* $db->query($sql,$table,$data); |
||||
* |
||||
* @param array $input - source array |
||||
* @param array $allowed - an array with allowed field names |
||||
* @return array filtered out source array |
||||
*/ |
||||
public function filterArray($input,$allowed) |
||||
{ |
||||
foreach(array_keys($input) as $key ) |
||||
@ -226,7 +441,7 @@ class SafeMySQL
|
||||
continue; |
||||
} |
||||
|
||||
$value = array_shift($args); |
||||
$value = array_shift($args); |
||||
switch ($part) |
||||
{ |
||||
case '?n': |
||||
@ -354,11 +569,22 @@ class SafeMySQL
|
||||
return $caller; |
||||
} |
||||
|
||||
/** |
||||
* Function to get last executed query. |
||||
* |
||||
* @return string|NULL either last executed query or NULL if were none |
||||
*/ |
||||
public function lastQuery() |
||||
{ |
||||
$last = end($this->stats); |
||||
return $last['query']; |
||||
} |
||||
|
||||
/** |
||||
* Function to get all query statistics. |
||||
* |
||||
* @return array contains all executed queries with timings |
||||
*/ |
||||
public function getStats() |
||||
{ |
||||
return $this->stats; |
||||
|
Loading…
Reference in new issue