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