diff --git a/safemysql.class.php b/safemysql.class.php new file mode 100644 index 0000000..89769a7 --- /dev/null +++ b/safemysql.class.php @@ -0,0 +1,365 @@ + '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; + } +}