PHP/MySQL implementation of SQL Functions
2
My previous entry on an easy to use SQLite interface for XUL/JS got me thinking: how many times have I had to write functions that were just wrappers for a little SQL?
Below is the fruits of that little query (excuse the pun). Forgive the dbXXX functions; my implementation depended on a small portion of a larger mysql library that I've grown accustomed to coding with.
Below is the fruits of that little query (excuse the pun). Forgive the dbXXX functions; my implementation depended on a small portion of a larger mysql library that I've grown accustomed to coding with.
// dbCall: Prepare a set of callable functions from SQL Query templates
//Example of easy use:
$zipcode=new dbCall(Array(
/* Escapables:
Structure: %tX - t: type, x: argument number
Types:
n: number (integer)
f: float (double)
b: boolean
i: immediate (not escaped - or recommended)
s: string
Argument number is which argument in the
resulting function is used as the result
*/
'getState'=>'SELECT STATE from zipcode ORDER BY ABS(ZIP-%n1) LIMIT 1'
));
$zipcode->getState(19145); //returns PA if the zipcode table is sane
//Not necessary, but makes it so that a DB Library is only prepared the first time
// it's called upon, and allows for calling from any scope.
//Use this as a template function if you have a table or set of
// tables which have multiple interrelated functions, and will be used anywhere
// the library it lives in will be used.
function zipcode() {
//Allows us to only ever prepare the functions once
static $functions;
if (!isset($functions))
$functions=new dbCall(Array(
'getState'=>'SELECT STATE from zipcode ORDER BY ABS(ZIP-%n1) LIMIT 1'
));
return $functions;
}
// simple call
echo zipcode()->getState(19145);
<?php
//Allows connection via
// mysql://user:pass@host/database
function dbConnect($uri=false) {
static $connection=false;
static $hup;
$uri=parse_url($uri);
foreach (Array('user','pass','host','path') as $v)
if (isset($uri[$v])) $$v=urldecode($uri[$v]);
else $$v='';
if (!empty($host) && !empty($user) && !empty($pass))
$srv='mysql://'.urlencode($user).':'.urlencode($pass).'@'.urlencode($host);
else
$srv=$hup;
if ($connection===false || $hup != $srv) {
if ($connection!==false) mysql_close($connection);
$connection=mysql_connect($host,$user,$pass);
$hup=$srv;
}
if (!empty($path)) {
if (@mysql_select_db(trim($path,'/'),$connection)===false)
return dbError();
return true;
}
if ($connection!==false)
return $connection;
return dbError();
}
//Alias for mysql_real_escape_string()
function dbEsc($t) {
return mysql_real_escape_string($t);
}
//returns array of error from last query, and last query
function dbError() {
return Array(
'error'=>mysql_error(),
'query'=>dbQuery()
);
}
//checks if a result value is an error
function is_dbError($r) {
if (is_object($r))
return !empty($r->error) && isset($r->query);
return false;
}
//Stores query, runs it, and returns the resource handle,
// or a dbError if there was a problem
function dbQuery($query=null) {
static $lastQuery;
if ($query===null) return $lastQuery;
$res=mysql_query($lastQuery=$query);
if ($res!==false) return $res;
return dbError();
}
//What this is all about!
class dbCall {
private $functions = Array();
private static $autoNonce;
//Prepares an SQL query
private static function prepare($q) {
if (!is_string($q)) return null;
preg_match_all(
//matches %iX, %bX, $nX, $fX, $sX
//which will become arguments for our function
'/(?<!\\\\)%(i|b|n|f|s)(\d+)/i',
$q, $results
);
$argct=0;
if (count($results[2])>0)
$argct=max($results[2]);
$args = Array();
$escs = Array();
$rq = '\''.addcslashes($q,'\\\'').'\'';
for ($i=1; $i<=$argct; $i++) {
$idx = array_search($i, $results[2]);
if ($idx===false) $args[]='$nr'.$i;
else {
$args[]=$arg='$'.$results[1][$idx].$i;
switch ($results[1][$idx]) {
//%nX = number of integer type. floats will get rounded.
//Non numerics cast to 0
case 'n':
$escs[]=$arg.'=round('.$arg.'*1);';
break;
//%fX = floating point number
//Non numerics cast to 0
case 'f':
$escs[]=$arg.'='.$arg.'*1;';
break;
//%bX = boolean
//Complex cast: mixed value of 1, true, 'yes', 'true', 'ok', 'y', or 't' are case to true
// All others cast to false.
case 'b':
$tmp='('.$arg.'==1 || '.$arg.'===true || preg_match(\'/^(?:yes|true|ok|y|t)$/\','.$arg.'))';
$escs[]=$arg.'='.$tmp.'?1:0;';
break;
//%iX = immediate; no escaping action required
// useful for imposing NOW()'s onto the database
// though, it'd really prefer an %nX filled with mktime() if you need to choose.
case 'i': break;
//default, will normally b %sX
//%sX = string or blob to be escaped.
default:
$escs[]=$arg.'=\'"\'.mysql_real_escape_string('.$arg.').\'"\';';
break;
}
//replace %tX with "'.$tX.'", so that the query is a proper PHP string.
$rq = str_replace($results[0][$idx],'\'.'.$arg.'.\'',$rq);
}
}
//Assume multiple results
$singleResult='false';
//... but use single if there's a LIMIT 1 in the query
if (preg_match('/[\s\)]LIMIT\s+1[\s\']/i', $rq))
$singleResult='true';
//join the arguments
$arguments = join(',',$args);
//render out the function
$function = //The created function, in all its glory:
//Escape the given arguments
join("\n",$escs)."\r\n".
//set the query string
'$query='.$rq.";\r\n".
//return the results of the query as rendered by dbAuto
'return dbCall::dbAuto($query, '.$singleResult.');';
return create_function($arguments, $function);
}
public function queryFunction($name, $sql) {
$this->functions[$name]=self::prepare($sql);
}
private function __call($name, $arguments) {
if (!isset($this->functions[$name]))
return trigger_error('Function '.$name.' was never prepared');
return call_user_func_array($this->functions[$name], $arguments);
}
public static function dbAuto($q, $singleResult=false) {
//make the query
$res=dbQuery($q);
//if $res is a bad value, return the error
if (is_dbError($res)) return $res;
$ins = @mysql_insert_id($res);
$ct = @mysql_num_rows($res);
$fld = @mysql_num_fields($res);
$err=mysql_errno();
//result of INSERT, UPDATE, DELETE, REPLACE, CREATE, DROP
if ($fld===false) {
//Affected an insert with AUTO_INCREMENT; return the last insert ID
if ($ins!=false) return $ins;
//Affected an UPDATE, REPLACE or DELETE, CREATE, DROP
// or an INSERT without AUTO_INCREMENT; return success
if ($err===0) return true;
//Something weird happened. Return dbError()
return dbError();
}
//Set up array for results
$ret=Array();
if ($fld==1) {
//Single file results; enumerate as list
while (false!==($en=mysql_fetch_row($res)))
$ret[]=$en[0];
} else {
//Multiple results; enumerate as list of objects
while (false!==($en=mysql_fetch_object($res)))
$ret[]=$en;
}
//Free up the result cache on the mysql
// interface; we've got what we need.
@mysql_free_result($res);
//No results? return a null
if (count($ret)==0) return null;
//LIMIT 1; just return the 1 result
if ($singleResult) return $ret[0];
//Return the results
return $ret;
}
public function __construct($obj=null) {
if ($obj===null) return;
foreach ((array)$obj as $name=>$query) {
$this->queryFunction($name,$query);
}
return;
}
}






There are currently no comments for this snippet.