PHP/MySQL implementation of SQL Functions





2
Date Submitted Wed. Aug. 13th, 2008 11:34 PM
Revision 1 of 1
Scripter Fordiman
Tags easy | mysql | PHP
Comments 0 comments
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.

// 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;
        }
}
 

Comments

There are currently no comments for this snippet.

Voting

Votes Up


Syntax Master dannyboy
Scripter Fordiman

Votes Down