1 changed files with 365 additions and 0 deletions
@ -0,0 +1,365 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class SafeMySQL |
||||||
|
{ |
||||||
|
private $conn; |
||||||
|
private $emode; |
||||||
|
private $exname; |
||||||
|
|
||||||
|
private $defaults = array( |
||||||
|
'host' => 'localhost', |
||||||
|
'user' => 'root', |
||||||
|
'pass' => '', |
||||||
|
'db' => 'test', |
||||||
|
'port' => NULL, |
||||||
|
'socket' => NULL, |
||||||
|
'pconnect' => FALSE, |
||||||
|
'charset' => 'utf8', |
||||||
|
'errmode' => 'error', //or exception |
||||||
|
'exception' => 'Exception', //Exception class name |
||||||
|
); |
||||||
|
|
||||||
|
const RESULT_ASSOC = MYSQLI_ASSOC; |
||||||
|
const RESULT_NUM = MYSQLI_NUM; |
||||||
|
|
||||||
|
function __construct($opt = array()) |
||||||
|
{ |
||||||
|
$opt = array_merge($this->defaults,$opt); |
||||||
|
|
||||||
|
$this->emode = $opt['errmode']; |
||||||
|
$this->exname = $opt['exception']; |
||||||
|
|
||||||
|
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 ) |
||||||
|
{ |
||||||
|
$this->error(mysqli_connect_errno()." ".mysqli_connect_error()); |
||||||
|
} |
||||||
|
|
||||||
|
mysqli_set_charset($this->conn, $opt['charset']) or $this->error(mysqli_error($this->conn)); |
||||||
|
unset($opt); // I am paranoid |
||||||
|
} |
||||||
|
|
||||||
|
public function query() |
||||||
|
{ |
||||||
|
$query = $this->prepareQuery(func_get_args()); |
||||||
|
$res = mysqli_query($this->conn, $query) or $this->error(mysqli_error($this->conn).". Full query: [$query]"); |
||||||
|
return $res; |
||||||
|
} |
||||||
|
public function fetch($result,$mode=self::RESULT_ASSOC) |
||||||
|
{ |
||||||
|
return mysqli_fetch_array($result, $mode); |
||||||
|
} |
||||||
|
|
||||||
|
public function affected_rows() |
||||||
|
{ |
||||||
|
return mysqli_affected_rows ($this->conn); |
||||||
|
} |
||||||
|
|
||||||
|
public function insert_id() |
||||||
|
{ |
||||||
|
return mysqli_insert_id($this->conn); |
||||||
|
} |
||||||
|
|
||||||
|
public function num_rows($result) |
||||||
|
{ |
||||||
|
return mysqli_num_rows($result); |
||||||
|
} |
||||||
|
|
||||||
|
public function free($result) |
||||||
|
{ |
||||||
|
mysqli_free_result($result); |
||||||
|
} |
||||||
|
|
||||||
|
public function getOne() |
||||||
|
{ |
||||||
|
$query = $this->prepareQuery(func_get_args()); |
||||||
|
if ( !preg_match('~LIMIT\s+\d+\s*(,\s*\d+\s*)?$~', $query) ) |
||||||
|
{ |
||||||
|
$query .= " LIMIT 1"; |
||||||
|
} |
||||||
|
if ($res = $this->query($query)) |
||||||
|
{ |
||||||
|
$row = $this->fetch($res); |
||||||
|
if (is_array($row)) { |
||||||
|
return reset($row); |
||||||
|
} |
||||||
|
$this->free($res); |
||||||
|
} |
||||||
|
return FALSE; |
||||||
|
} |
||||||
|
|
||||||
|
public function getRow() |
||||||
|
{ |
||||||
|
$query = $this->prepareQuery(func_get_args()); |
||||||
|
if ( !preg_match('~LIMIT\s+\d+\s*(,\s*\d+\s*)?$~',$query) ) |
||||||
|
{ |
||||||
|
$query.= " LIMIT 1"; |
||||||
|
} |
||||||
|
|
||||||
|
if ($res = $this->query($query)) { |
||||||
|
$ret = $this->fetch($res); |
||||||
|
$this->free($res); |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
return FALSE; |
||||||
|
} |
||||||
|
|
||||||
|
public function getCol() |
||||||
|
{ |
||||||
|
$ret = array(); |
||||||
|
$query = $this->prepareQuery(func_get_args()); |
||||||
|
if ( $res = $this->query($query) ) |
||||||
|
{ |
||||||
|
while($row = $this->fetch($res)) |
||||||
|
{ |
||||||
|
$ret[] = reset($row); |
||||||
|
} |
||||||
|
$this->free($res); |
||||||
|
} |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
public function getAll() |
||||||
|
{ |
||||||
|
$ret = array(); |
||||||
|
$query = $this->prepareQuery(func_get_args()); |
||||||
|
if ( $res = $this->query($query) ) |
||||||
|
{ |
||||||
|
while($row = $this->fetch($res)) |
||||||
|
{ |
||||||
|
$ret[] = $row; |
||||||
|
} |
||||||
|
$this->free($res); |
||||||
|
} |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
public function getInd() |
||||||
|
{ |
||||||
|
$args = func_get_args(); |
||||||
|
$index = array_shift($args); |
||||||
|
$query = $this->prepareQuery($args); |
||||||
|
|
||||||
|
$ret = array(); |
||||||
|
if ( $res = $this->query($query) ) |
||||||
|
{ |
||||||
|
while($row = $this->fetch($res)) |
||||||
|
{ |
||||||
|
$ret[$row[$index]] = $row; |
||||||
|
} |
||||||
|
$this->free($res); |
||||||
|
} |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
public function getIndCol() |
||||||
|
{ |
||||||
|
$args = func_get_args(); |
||||||
|
$index = array_shift($args); |
||||||
|
$query = $this->prepareQuery($args); |
||||||
|
|
||||||
|
$ret = array(); |
||||||
|
if ( $res = $this->query($query) ) |
||||||
|
{ |
||||||
|
while($row = $res->fetch($res)) |
||||||
|
{ |
||||||
|
$key = $row[$index]; |
||||||
|
unset($row[$index]); |
||||||
|
$ret[$key] = reset($row); |
||||||
|
} |
||||||
|
$this->free($res); |
||||||
|
} |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
public function parse() |
||||||
|
{ |
||||||
|
return $this->prepareQuery(func_get_args()); |
||||||
|
} |
||||||
|
|
||||||
|
public function whiteList($input,$allowed,$strict=FALSE) |
||||||
|
{ |
||||||
|
$found = array_search($input); |
||||||
|
if ($strict && ($found === FALSE)) |
||||||
|
{ |
||||||
|
return FALSE; |
||||||
|
} else { |
||||||
|
return $allowed[$found]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function filterArray($input,$allowed) |
||||||
|
{ |
||||||
|
foreach(array_keys($input) as $key ) |
||||||
|
{ |
||||||
|
if ( !in_array($key,$allowed) ) |
||||||
|
{ |
||||||
|
unset($input[$key]); |
||||||
|
} |
||||||
|
} |
||||||
|
return $input; |
||||||
|
} |
||||||
|
private function prepareQuery($args) |
||||||
|
{ |
||||||
|
$raw = $query = array_shift($args); |
||||||
|
preg_match_all('~(\?[a-z?])~',$query,$m,PREG_OFFSET_CAPTURE); |
||||||
|
$pholders = $m[1]; |
||||||
|
$count = 0; |
||||||
|
foreach ($pholders as $i => $p) |
||||||
|
{ |
||||||
|
if ($p[0] != '??') |
||||||
|
{ |
||||||
|
$count++; |
||||||
|
} |
||||||
|
} |
||||||
|
if ( $count != count($args) ) |
||||||
|
{ |
||||||
|
$this->error("Number of args (".count($args).") doesn't match number of placeholders ($count) in [$raw]"); |
||||||
|
} |
||||||
|
$shift = 0; |
||||||
|
$qmarks = 0; |
||||||
|
foreach ($pholders as $i => $p) |
||||||
|
{ |
||||||
|
$pholder = $p[0]; |
||||||
|
$offset = $p[1] + $shift; |
||||||
|
if ($pholder != '??') |
||||||
|
{ |
||||||
|
$value = $args[$i-$qmarks]; |
||||||
|
} |
||||||
|
switch ($pholder) |
||||||
|
{ |
||||||
|
case '?n': |
||||||
|
$value = $this->escapeIdent($value); |
||||||
|
break; |
||||||
|
case '?s': |
||||||
|
$value = $this->escapeString($value); |
||||||
|
break; |
||||||
|
case '?i': |
||||||
|
$value = $this->escapeInt($value); |
||||||
|
break; |
||||||
|
case '?a': |
||||||
|
$value = $this->createIN($value); |
||||||
|
break; |
||||||
|
case '?u': |
||||||
|
$value = $this->createSET($value); |
||||||
|
break; |
||||||
|
case '??': |
||||||
|
$value = '?'; |
||||||
|
$qmarks++; |
||||||
|
break; |
||||||
|
default: |
||||||
|
$this->error("Unknown placeholder type ($pholder) in [$raw]"); |
||||||
|
} |
||||||
|
$query = substr_replace($query,$value,$offset,2); |
||||||
|
$shift+= strlen($value) - strlen($pholder); |
||||||
|
} |
||||||
|
$this->lastquery = $query; |
||||||
|
return $query; |
||||||
|
} |
||||||
|
|
||||||
|
private function escapeInt($value) |
||||||
|
{ |
||||||
|
if (is_float($value)) |
||||||
|
{ |
||||||
|
return number_format($value, 0, '.', ''); // may lose precision on big numbers |
||||||
|
} |
||||||
|
elseif(is_numeric($value)) |
||||||
|
{ |
||||||
|
return (string)$value; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
$this->error("Invalid value for ?i (int) placeholder: [$value](".gettype($value).")"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function escapeString($value) |
||||||
|
{ |
||||||
|
return "'".mysqli_real_escape_string($this->conn,$value)."'"; |
||||||
|
} |
||||||
|
|
||||||
|
private function escapeIdent($value) |
||||||
|
{ |
||||||
|
if ($value) |
||||||
|
{ |
||||||
|
return "`".str_replace("`","``",$value)."`"; |
||||||
|
} else { |
||||||
|
$this->error("Empty value for ?n (identifier) placeholder."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function createIN($data) |
||||||
|
{ |
||||||
|
if (!is_array($data)) |
||||||
|
{ |
||||||
|
$this->error("Value for ?a (IN) placeholder should be array."); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (!$data) |
||||||
|
{ |
||||||
|
return 'NULL'; |
||||||
|
} |
||||||
|
$query = $comma = ''; |
||||||
|
foreach ($data as $key => $value) |
||||||
|
{ |
||||||
|
$query .= $comma.$this->escapeString($value); |
||||||
|
$comma = ","; |
||||||
|
} |
||||||
|
return $query; |
||||||
|
} |
||||||
|
|
||||||
|
private function createSET($data) |
||||||
|
{ |
||||||
|
if (!is_array($data)) |
||||||
|
{ |
||||||
|
$this->error("Value for ?u (SET) placeholder should be an array. ".gettype($data)." passed instead."); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (!$data) |
||||||
|
{ |
||||||
|
$this->error("Empty array for ?u (SET) placeholder."); |
||||||
|
return; |
||||||
|
} |
||||||
|
$query = $comma = ''; |
||||||
|
foreach ($data as $key => $value) |
||||||
|
{ |
||||||
|
$query .= $comma.$this->escapeIdent($key).'='.$this->escapeString($value); |
||||||
|
$comma = ","; |
||||||
|
} |
||||||
|
return $query; |
||||||
|
} |
||||||
|
|
||||||
|
private function error($err) |
||||||
|
{ |
||||||
|
$err = __CLASS__.": ".$err; |
||||||
|
|
||||||
|
if ( $this->emode == 'error' ) |
||||||
|
{ |
||||||
|
$err .= ". Error initiated in ".$this->caller().", thrown"; |
||||||
|
trigger_error($err,E_USER_ERROR); |
||||||
|
} else { |
||||||
|
throw new $this->exname($err); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function caller() |
||||||
|
{ |
||||||
|
$trace = debug_backtrace(); |
||||||
|
$caller = ''; |
||||||
|
foreach ($trace as $t) |
||||||
|
{ |
||||||
|
if ( isset($t['class']) && $t['class'] == __CLASS__ ) |
||||||
|
{ |
||||||
|
$caller = $t['file']." on line ".$t['line']; |
||||||
|
} else { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
return $caller; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue