Compare commits

...

15 Commits
dev ... master

Author SHA1 Message Date
Misterzym 62351242d8 Мои изменения в распространяемую библиотеку 7 years ago
colshrapnel e1cbace64e
remove explicit version from composer.json 7 years ago
colshrapnel 4f76f45271
Merge pull request #48 from Habetdin/patch-1 7 years ago
Habetdin 4977ba1e86
Fix small typo 7 years ago
colshrapnel 5fcb267fbf Methods made protected instead of private 8 years ago
colshrapnel ccbf8c3332 Properties made protected instead of private 8 years ago
colshrapnel 5eba26f429 Merge pull request #40 from colshrapnel/binary-test 8 years ago
colshrapnel bdb8cf6ba5 Delete .gitattributes 8 years ago
colshrapnel 1d851923c8 Create README.md 8 years ago
colshrapnel 0afa8ddfc2 Delete README.md 8 years ago
colshrapnel c1cb308188 Create .gitattributes 8 years ago
colshrapnel 4a2ff95fe4 Merge pull request #39 from PeterMortensen/master 8 years ago
Peter Mortensen 26c7cdfeba Update README.md 8 years ago
Peter Mortensen 0059986eec Update README.md 8 years ago
colshrapnel 9360eabc1c Merge pull request #32 from colshrapnel/dev 10 years ago
  1. 37
      README.md
  2. 1
      composer.json
  3. 258
      safemysql.class.php

37
README.md

@ -1,21 +1,21 @@
SafeMySQL
=========
SafeMySQL is a PHP class for safe and convenient handling of Mysql queries.
- safe because <b>every</b> dynamic query part goes into query via <b>placeholder</b>
- convenient because it makes application code short and meaningful, without useless repetitions, making it Extra <abbr title="Don't Repeat Yourself">DRY</abbr>
SafeMySQL is a PHP class for safe and convenient handling of MySQL queries.
- Safe because <b>every</b> dynamic query part goes into the query via <b>placeholder</b>
- Convenient because it makes application code short and meaningful, without useless repetitions, making it ''extra'' <abbr title="Don't Repeat Yourself">DRY</abbr>
This class is distinguished by three main features
- unlike standard libraries, it is using **type-hinted placeholders**, for the **everything** that may be put into query
- unlike standard libraries, it require no repetitive binding, fetching and such,
- Unlike standard libraries, it is using **type-hinted placeholders**, for the **everything** that may be put into the query
- Unlike standard libraries, it requires no repetitive binding, fetching and such,
thanks to set of helper methods to get the desired result right out of the query
- unlike standard libraries, it can parse placeholders not in the whole query only, but in the arbitary query part,
thanks to indispensabe **parse()** method, making complex queries as easy and safe as regular ones.
- Unlike standard libraries, it can parse placeholders not in the whole query only, but in the arbitary query part,
thanks to the indispensabe **parse()** method, making complex queries as easy and safe as regular ones.
Yet it is very easy to use. You need to learn only few things:
Yet, it is very easy to use. You need to learn only a few things:
1. You have to **always** pass whatever dynamical data into query via *placeholder*
2. Each placeholder have to be marked with data type. At the moment there are 6 types:
1. You have to **always** pass whatever dynamical data into the query via *placeholder*
2. Each placeholder have to be marked with data type. At the moment there are six types:
* ?s ("string") - strings (also ```DATE```, ```FLOAT``` and ```DECIMAL```)
* ?i ("integer") - the name says it all
* ?n ("name") - identifiers (table and field names)
@ -30,7 +30,7 @@ Yet it is very easy to use. You need to learn only few things:
* getAll($query,$param1,$param2, ...) - returns 2-dimensional array, an array of rows
* getInd($key,$query,$par1,$par2, ...) - returns an indexed 2-dimensional array, an array of rows
* getIndCol($key,$query,$par1,$par2, ...) - returns 1-dimensional array, an indexed column, consists of key => value pairs
4. For the whatever complex case always use **parse()** method. And insert already parsed parts via **?p** placeholder
4. For the whatever complex case always use the **parse()** method. And insert
The rest is as usual - just create a regular SQL (with placeholders) and get a result:
@ -39,16 +39,16 @@ The rest is as usual - just create a regular SQL (with placeholders) and get a r
* ```$data = $db->getAll("SELECT * FROM ?n WHERE mod=?s LIMIT ?i",$table,$mod,$limit);```
The main feature of this class is a <i>type-hinted placeholders</i>.
And it's really great step further from just ordinal placeholders used in prepared statements.
And it's a really great step further from just ordinal placeholders used in prepared statements.
Simply because <b>dynamical parts of the query aren't limited to just scalar data!</b>
In the real life we have to add identifiers, arrays for ```IN``` operator, arrays for ```INSERT``` and ```UPDATE``` queries.
In the real life we have to add identifiers, arrays for ```IN``` operator, and arrays for ```INSERT``` and ```UPDATE``` queries.
So - we need <b>many</b> different types of data formatting. Thus, we need the way to tell the driver how to format this particular data.
Conventional prepared statements use toilsome and repeating bind_* functions.
But there is a way more sleek and useful way - to set the type along with placeholder itself. It is not something new - well-known ```printf()``` function uses exactly the same mechanism. So, I hesitated not to borrow such a brilliant idea.
To implement such a feature, no doubt one have to have their own query parser. No problem, it's not a big deal. But the benefits are innumerable.
Look at all the questions on Stackoverflow where developers trying in vain to bind a field name.
Voila - with identifier placeholder it is as easy as adding a field value:
Look at all the questions on Stack Overflow where developers are trying in vain to bind a field name.
Voila - with the identifier placeholder it is as easy as adding a field value:
```php
$field = $_POST['field'];
@ -62,14 +62,13 @@ Nothing could be easier!
Of course we will have placeholders for the common types - strings and numbers.
But as we started inventing new placeholders - let's make some more!
Another trouble in creating prepared queries - arrays going to IN operator. Everyone is trying to do it their own way but the type-hinted placeholder makes it as simple as adding a string:
Another trouble in creating prepared queries - arrays going to the IN operator. Everyone is trying to do it their own way, but the type-hinted placeholder makes it as simple as adding a string:
```php
$array = array(1,2,3);
$data = $db->query("SELECT * FROM table WHERE id IN (?a)",$array);
```
Same goes for such toilsome queries like ```INSERT``` and ```UPDATE```.
And, of course, we have a set of helper functions to turn type-hinted placeholders into real brilliant, making almost every call to database as simple as 1 or 2 lines of code for all the regular real life tasks.
The same goes for such toilsome queries like ```INSERT``` and ```UPDATE```.
And, of course, we have a set of helper functions to turn type-hinted placeholders into real brilliant, making almost every call to the database as simple as one or two lines of code for all the regular real life tasks.

1
composer.json

@ -2,7 +2,6 @@
"name": "colshrapnel/safemysql",
"description": "A real safe and convenient way to handle MySQL queries.",
"type": "library",
"version": "1.0.0",
"keywords": [
"db",
"mysql"

258
safemysql.class.php

@ -1,4 +1,5 @@
<?php
/**
* @author col.shrapnel@gmail.com
* @link http://phpfaq.ru/safemysql
@ -65,16 +66,64 @@
* $data = $db->getAll("SELECT * FROM table WHERE ?p", $bar, $sqlpart);
*
*/
class SafeMySQL {
#########################ТВИКИ ОТ ЗУМА######################
class SafeMySQL
{
/**
* удаляет строки из таблицы, принимает массив вида
* 0=>имя таблицы
* 1=>поле в таблице
* 2=>чему равно
*
* @param array $array
*/
public function removeRows($array) {
foreach ($array as $task) {
$this->query("DELETE FROM ?n WHERE ?n = ?i", $task[0], $task[1], $task[2]);
//$wpdb->delete($task[0], array($task[1] => $task[2]));
}
}
private $conn;
private $stats;
private $emode;
private $exname;
/**
* Возвращает sql для объединения таблиц
* Принимает массива вида
* array(array('name'=>'table1','field'=>'field1'),array('name'=>'table2','field'=>'field2'));
*
* @param string $sql
*/
public function selectLeftJoin($tables) {
$sql = "SELECT * FROM `" . $tables . "`";
foreach ($ons as $k => $table) {
$sql .= " LEFT JOIN `" . $table[0]['name'] . "` ON `" . $table[1]['name'] . "`.`" . $table[1]['field'] . "`=`" . $table[0]['name'] . "`.`" . $table[0]['field'] . "`";
}
return $sql;
}
private $defaults = array(
/**
* Возвращает стандартную функцию обновления
*
* @return string UPDATE ?n SET ?u WHERE `id` = ?i
*/
public function updTempl() {
return "UPDATE ?n SET ?u WHERE `id` = ?i";
}
/**
* Возвращает стандартную функцию вставки
*
* @return string INSERT INTO ?n SET ?u
*/
public function insTempl() {
return "INSERT INTO ?n SET ?u";
}
############################################################
protected $conn;
protected $stats;
protected $emode;
protected $exname;
protected $defaults = array(
'host' => 'localhost',
'user' => 'root',
'pass' => '',
@ -90,34 +139,28 @@ class SafeMySQL
const RESULT_ASSOC = MYSQLI_ASSOC;
const RESULT_NUM = MYSQLI_NUM;
function __construct($opt = array())
{
function __construct($opt = array()) {
$opt = array_merge($this->defaults, $opt);
$this->emode = $opt['errmode'];
$this->exname = $opt['exception'];
if (isset($opt['mysqli']))
{
if ($opt['mysqli'] instanceof mysqli)
{
if (isset($opt['mysqli'])) {
if ($opt['mysqli'] instanceof mysqli) {
$this->conn = $opt['mysqli'];
return;
} else {
$this->error("mysqli option must be valid instance of mysqli class");
}
}
if ($opt['pconnect'])
{
if ($opt['pconnect']) {
$opt['host'] = "p:" . $opt['host'];
}
@$this->conn = mysqli_connect($opt['host'], $opt['user'], $opt['pass'], $opt['db'], $opt['port'], $opt['socket']);
if ( !$this->conn )
{
if (!$this->conn) {
$this->error(mysqli_connect_errno() . " " . mysqli_connect_error());
}
@ -135,8 +178,7 @@ class SafeMySQL
* @param mixed $arg,... unlimited number of arguments to match placeholders in the query
* @return resource|FALSE whatever mysqli_query returns
*/
public function query()
{
public function query() {
return $this->rawQuery($this->prepareQuery(func_get_args()));
}
@ -147,8 +189,7 @@ class SafeMySQL
* @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)
{
public function fetch($result, $mode = self::RESULT_ASSOC) {
return mysqli_fetch_array($result, $mode);
}
@ -157,8 +198,7 @@ class SafeMySQL
*
* @return int whatever mysqli_affected_rows returns
*/
public function affectedRows()
{
public function affectedRows() {
return mysqli_affected_rows($this->conn);
}
@ -167,8 +207,7 @@ class SafeMySQL
*
* @return int whatever mysqli_insert_id returns
*/
public function insertId()
{
public function insertId() {
return mysqli_insert_id($this->conn);
}
@ -178,16 +217,14 @@ class SafeMySQL
* @param resource $result - myqli result
* @return int whatever mysqli_num_rows returns
*/
public function numRows($result)
{
public function numRows($result) {
return mysqli_num_rows($result);
}
/**
* Conventional function to free the resultset.
*/
public function free($result)
{
public function free($result) {
mysqli_free_result($result);
}
@ -202,11 +239,9 @@ class SafeMySQL
* @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()
{
public function getOne() {
$query = $this->prepareQuery(func_get_args());
if ($res = $this->rawQuery($query))
{
if ($res = $this->rawQuery($query)) {
$row = $this->fetch($res);
if (is_array($row)) {
return reset($row);
@ -221,14 +256,13 @@ class SafeMySQL
*
* Examples:
* $data = $db->getRow("SELECT * FROM table WHERE id=1");
* $data = $db->getOne("SELECT * FROM table WHERE id=?i", $id);
* $data = $db->getRow("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()
{
public function getRow() {
$query = $this->prepareQuery(func_get_args());
if ($res = $this->rawQuery($query)) {
$ret = $this->fetch($res);
@ -249,14 +283,11 @@ class SafeMySQL
* @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()
{
public function getCol() {
$ret = array();
$query = $this->prepareQuery(func_get_args());
if ( $res = $this->rawQuery($query) )
{
while($row = $this->fetch($res))
{
if ($res = $this->rawQuery($query)) {
while ($row = $this->fetch($res)) {
$ret[] = reset($row);
}
$this->free($res);
@ -275,14 +306,11 @@ class SafeMySQL
* @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()
{
public function getAll() {
$ret = array();
$query = $this->prepareQuery(func_get_args());
if ( $res = $this->rawQuery($query) )
{
while($row = $this->fetch($res))
{
if ($res = $this->rawQuery($query)) {
while ($row = $this->fetch($res)) {
$ret[] = $row;
}
$this->free($res);
@ -302,17 +330,14 @@ class SafeMySQL
* @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()
{
public function getInd() {
$args = func_get_args();
$index = array_shift($args);
$query = $this->prepareQuery($args);
$ret = array();
if ( $res = $this->rawQuery($query) )
{
while($row = $this->fetch($res))
{
if ($res = $this->rawQuery($query)) {
while ($row = $this->fetch($res)) {
$ret[$row[$index]] = $row;
}
$this->free($res);
@ -331,17 +356,14 @@ class SafeMySQL
* @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()
{
public function getIndCol() {
$args = func_get_args();
$index = array_shift($args);
$query = $this->prepareQuery($args);
$ret = array();
if ( $res = $this->rawQuery($query) )
{
while($row = $this->fetch($res))
{
if ($res = $this->rawQuery($query)) {
while ($row = $this->fetch($res)) {
$key = $row[$index];
unset($row[$index]);
$ret[$key] = reset($row);
@ -373,8 +395,7 @@ class SafeMySQL
* @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()
{
public function parse() {
return $this->prepareQuery(func_get_args());
}
@ -398,8 +419,7 @@ class SafeMySQL
* @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)
{
public function whiteList($input, $allowed, $default = FALSE) {
$found = array_search($input, $allowed);
return ($found === FALSE) ? $default : $allowed[$found];
}
@ -420,12 +440,9 @@ class SafeMySQL
* @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 )
{
if ( !in_array($key,$allowed) )
{
public function filterArray($input, $allowed) {
foreach (array_keys($input) as $key) {
if (!in_array($key, $allowed)) {
unset($input[$key]);
}
}
@ -437,8 +454,7 @@ class SafeMySQL
*
* @return string|NULL either last executed query or NULL if were none
*/
public function lastQuery()
{
public function lastQuery() {
$last = end($this->stats);
return $last['query'];
}
@ -448,20 +464,18 @@ class SafeMySQL
*
* @return array contains all executed queries with timings and errors
*/
public function getStats()
{
public function getStats() {
return $this->stats;
}
/**
* private function which actually runs a query against Mysql server.
* protected function which actually runs a query against Mysql server.
* also logs some stats like profiling info and error message
*
* @param string $query - a regular SQL query
* @return mysqli result resource or FALSE on error
*/
private function rawQuery($query)
{
protected function rawQuery($query) {
$start = microtime(TRUE);
$res = mysqli_query($this->conn, $query);
$timer = microtime(TRUE) - $start;
@ -471,8 +485,7 @@ class SafeMySQL
'start' => $start,
'timer' => $timer,
);
if (!$res)
{
if (!$res) {
$error = mysqli_error($this->conn);
end($this->stats);
@ -486,29 +499,24 @@ class SafeMySQL
return $res;
}
private function prepareQuery($args)
{
protected function prepareQuery($args) {
$query = '';
$raw = array_shift($args);
$array = preg_split('~(\?[nsiuap])~u', $raw, null, PREG_SPLIT_DELIM_CAPTURE);
$anum = count($args);
$pnum = floor(count($array) / 2);
if ( $pnum != $anum )
{
if ($pnum != $anum) {
$this->error("Number of args ($anum) doesn't match number of placeholders ($pnum) in [$raw]");
}
foreach ($array as $i => $part)
{
if ( ($i % 2) == 0 )
{
foreach ($array as $i => $part) {
if (($i % 2) == 0) {
$query .= $part;
continue;
}
$value = array_shift($args);
switch ($part)
{
switch ($part) {
case '?n':
$part = $this->escapeIdent($value);
break;
@ -533,90 +541,72 @@ class SafeMySQL
return $query;
}
private function escapeInt($value)
{
if ($value === NULL)
{
protected function escapeInt($value) {
if ($value === NULL) {
return 'NULL';
}
if(!is_numeric($value))
{
if (!is_numeric($value)) {
$this->error("Integer (?i) placeholder expects numeric value, " . gettype($value) . " given");
return FALSE;
}
if (is_float($value))
{
if (is_float($value)) {
$value = number_format($value, 0, '.', ''); // may lose precision on big numbers
}
return $value;
}
private function escapeString($value)
{
if ($value === NULL)
{
protected function escapeString($value) {
if ($value === NULL) {
return 'NULL';
}
return "'" . mysqli_real_escape_string($this->conn, $value) . "'";
}
private function escapeIdent($value)
{
if ($value)
{
protected function escapeIdent($value) {
if ($value) {
return "`" . str_replace("`", "``", $value) . "`";
} else {
$this->error("Empty value for identifier (?n) placeholder");
}
}
private function createIN($data)
{
if (!is_array($data))
{
protected function createIN($data) {
if (!is_array($data)) {
$this->error("Value for IN (?a) placeholder should be array");
return;
}
if (!$data)
{
if (!$data) {
return 'NULL';
}
$query = $comma = '';
foreach ($data as $value)
{
foreach ($data as $value) {
$query .= $comma . $this->escapeString($value);
$comma = ",";
}
return $query;
}
private function createSET($data)
{
if (!is_array($data))
{
protected function createSET($data) {
if (!is_array($data)) {
$this->error("SET (?u) placeholder expects array, " . gettype($data) . " given");
return;
}
if (!$data)
{
if (!$data) {
$this->error("Empty array for SET (?u) placeholder");
return;
}
$query = $comma = '';
foreach ($data as $key => $value)
{
foreach ($data as $key => $value) {
$query .= $comma . $this->escapeIdent($key) . '=' . $this->escapeString($value);
$comma = ",";
}
return $query;
}
private function error($err)
{
protected function error($err) {
$err = __CLASS__ . ": " . $err;
if ( $this->emode == 'error' )
{
if ($this->emode == 'error') {
$err .= ". Error initiated in " . $this->caller() . ", thrown";
trigger_error($err, E_USER_ERROR);
} else {
@ -624,14 +614,11 @@ class SafeMySQL
}
}
private function caller()
{
protected function caller() {
$trace = debug_backtrace();
$caller = '';
foreach ($trace as $t)
{
if ( isset($t['class']) && $t['class'] == __CLASS__ )
{
foreach ($trace as $t) {
if (isset($t['class']) && $t['class'] == __CLASS__) {
$caller = $t['file'] . " on line " . $t['line'];
} else {
break;
@ -644,13 +631,12 @@ class SafeMySQL
* On a long run we can eat up too much memory with mere statsistics
* Let's keep it at reasonable size, leaving only last 100 entries.
*/
private function cutStats()
{
if ( count($this->stats) > 100 )
{
protected function cutStats() {
if (count($this->stats) > 100) {
reset($this->stats);
$first = key($this->stats);
unset($this->stats[$first]);
}
}
}

Loading…
Cancel
Save