562 lines
20 KiB
PHP
562 lines
20 KiB
PHP
<?php
|
|
/*
|
|
APIQL v. 0.3
|
|
|
|
Name : APIQL
|
|
Author : Temperini Mirko
|
|
Description : a custom api builder based on js notation,
|
|
to semplify you development application.
|
|
With this tool you can build your query with your custom
|
|
syntax/command
|
|
Version : 0.3
|
|
Date : 2011-07-17
|
|
Email : dottwatson@gmail.com
|
|
Licence : GPL
|
|
|
|
|
|
|
|
|
|
APIQL gives you the possibility to build your own queries with data,personal syntax and commands.
|
|
It is based on a model where you can define in order:
|
|
-if the command in required or optional
|
|
-the command name
|
|
-the command data accepted. The command data can be:
|
|
-string
|
|
-int
|
|
-float
|
|
-boolean
|
|
-null
|
|
-array a javascript array notation e.g. ["bar","foo",24]
|
|
-json a json string e.g. {bar:foo,"surname":doe,age:32}
|
|
-php code
|
|
|
|
APIQL supports subqueries
|
|
|
|
APIQL supports events as 'beforeQuery' , 'afterQuery' and 'error'
|
|
|
|
APIQL gives you a nice tool for convert not well formed json strings into a valid PHP JSON strings
|
|
|
|
|
|
How it works?
|
|
|
|
step 1: declare a model
|
|
step 2: register model and bind it to function
|
|
step 3: call your query
|
|
|
|
|
|
|
|
|
|
e.g.
|
|
|
|
|
|
apiql::register('!set/!new friend[json]','new_friend');
|
|
|
|
apiql::query('set new friend {name:bar,surname:foo}',25);
|
|
|
|
function new_friend($sql,$age){
|
|
echo 'you have requested to set a new friend as<br />';
|
|
var_dump($sql['new friend']);
|
|
echo "<br />the age of {$sql['new friend']['name']} is $age<br />";
|
|
}
|
|
|
|
|
|
See examples how to configure and use APIQL
|
|
*/
|
|
|
|
|
|
|
|
class apiql{
|
|
public static $syntax = array();
|
|
public static $callback = '';
|
|
public static $isValid = false;
|
|
public static $data = null;
|
|
public static $error = '';
|
|
public static $events = array(
|
|
'afterQuery' =>array(),
|
|
'beforeQuery' =>array(),
|
|
'error' =>array(),
|
|
);
|
|
|
|
private static $config=array(
|
|
'check_func_exists'=>false,
|
|
'display_errors'=>false,
|
|
'error_level'=>E_USER_WARNING
|
|
);
|
|
|
|
|
|
/*
|
|
* access: public
|
|
* description: sets your class configuration to prevent
|
|
* or not function exixtence,display errors
|
|
* vars: what [string] the configuration param name
|
|
* val [bool/int] the value of the param
|
|
*
|
|
*returns: null
|
|
*/
|
|
public static function set($what='',$val=false){
|
|
$what=(string)$what;
|
|
if(isset(self::$config[$what])){
|
|
self::$config[$what] = $val;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* access: public
|
|
*
|
|
* description: get your class configuration param value
|
|
*
|
|
* vars: what [string] the configuration param name
|
|
*
|
|
*returns: the value on success, null on faailure
|
|
*/
|
|
public static function get($what=''){
|
|
$what=(string)$what;
|
|
if(isset(self::$config[$what])){
|
|
return self::$config[$what];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
/*
|
|
* access: public
|
|
* vars: model [string] the query model to parse
|
|
* fn [string] the function name to bind the query
|
|
*
|
|
* return: true on success or false on failure
|
|
*/
|
|
public static function register($model='',$fn=''){
|
|
$elements=explode('/',$model);
|
|
foreach($elements as $el){
|
|
$tmp=array(
|
|
'model' =>trim($el),
|
|
'required' =>false,
|
|
'data_type' =>array(),
|
|
'callback' =>''
|
|
);
|
|
|
|
preg_match('/^
|
|
(?P<required>!|\?)
|
|
(?P<name>[a-z0-9\s]+)
|
|
(?P<data_type>(\[([a-z\s]+,?)+\])|$)
|
|
/xi',$el,$blocks);
|
|
|
|
|
|
if(empty($blocks)){
|
|
self::trigger_error("apiql query => invalid block `{$el}`");
|
|
return self::reset();
|
|
}
|
|
|
|
$tmp['required']=($blocks['required'] == '!')?true:false;
|
|
if(trim($blocks['data_type']) != ''){
|
|
$data=explode(',',substr($blocks['data_type'],1,-1));
|
|
foreach($data as $type){
|
|
$type=trim($type);
|
|
if(preg_match('/^(string|int|float|null|boolean|json|array|php)$/i',$type)){
|
|
$tmp['data_type'][]=strtolower($type);
|
|
}
|
|
else{
|
|
self::trigger_error("apiql query => invalid data type `{$type}`");
|
|
return self::reset(true);
|
|
}
|
|
}
|
|
}
|
|
self::$syntax[$blocks['name']]=$tmp;
|
|
}
|
|
|
|
if(!function_exists($fn) && self::$config['check_func_exists'] == true){
|
|
self::trigger_error("apiql query => undefined function `{$fn}` as callback");
|
|
return self::reset();
|
|
}
|
|
else{
|
|
self::$callback = $fn;
|
|
}
|
|
self::compile();
|
|
}
|
|
|
|
|
|
/*
|
|
* access: private
|
|
* description: try to compile the query ad register it
|
|
* vars: none
|
|
*
|
|
*returns: true on success or false on failure
|
|
*/
|
|
|
|
private static function compile(){
|
|
$const=array();
|
|
foreach(self::$syntax as $el){
|
|
$const[]=$el['model'];
|
|
}
|
|
|
|
$const =md5(implode('/',$const));
|
|
$const ='apiql_'.$const;
|
|
$value =array('query'=>self::$syntax,'callback'=>self::$callback);
|
|
$value =serialize($value);
|
|
if(!defined($const)){
|
|
self::reset();
|
|
return define($const,$value,true);
|
|
}
|
|
else{
|
|
self::trigger_error("unable to register query: query is already defined");
|
|
return self::reset(true);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* access: public
|
|
*
|
|
* description: execute your query and bind its results on your binded function
|
|
*
|
|
* vars: req [string] the query to be executed
|
|
* [,opt1,op2,opt3...] all extra arguments will be
|
|
* passed to the binded function 'as is'
|
|
*
|
|
* return: your query results on success or false on failure
|
|
*/
|
|
public static function query($req=''){
|
|
$extra_data=func_get_args();
|
|
$not_extra=array_shift($extra_data);
|
|
|
|
$requested_reg='(?<requested>)';
|
|
$constants=get_defined_constants(true);
|
|
$constants=(isset($constants['user']))?$constants['user']:array();
|
|
foreach($constants as $ckey=>$const){
|
|
if(preg_match('/^apiql_[a-f0-9]{32}$/i',$ckey)){
|
|
//extrasc infos on constant name
|
|
$qry_data=unserialize($const);
|
|
//var_dump($qry_data);
|
|
$reg=array();
|
|
$cnt=0;
|
|
foreach($qry_data['query'] as $name=>$el){
|
|
$data_type='';
|
|
if(!empty($el['data_type'])){
|
|
$data_type=array();
|
|
foreach($el['data_type'] as $data_value){
|
|
if($data_value=='string') $data_type[] ='\'[^\']*\'';
|
|
elseif($data_value=='int') $data_type[] ='((\'\-?\d+\')|\-?(\d+))';
|
|
elseif($data_value=='float') $data_type[] ='((\'\-?(\d+)?\.\d+\')|\-?(\d+)?\.\d+)';
|
|
elseif($data_value=='null') $data_type[] ='(null)';
|
|
elseif($data_value=='boolean') $data_type[] ='(true|false)';
|
|
elseif($data_value=='json') $data_type[] ='(\{[^\}]+\})';
|
|
elseif($data_value=='array') $data_type[] ='(\[[^\]]+\])';
|
|
elseif($data_value=='php') $data_type[] ='(\(.+\))';
|
|
}
|
|
|
|
$data_type[]='apiql::query\([[:space:]]*("[^"]*"|\'[^\']*\')[[:space:]]*\)';
|
|
$data_type='[[:space:]]+(?P<data_'.$cnt.'>'.implode($data_type,'|').')';
|
|
}
|
|
|
|
$name=preg_quote($name,'/');
|
|
$name=preg_replace('/[[:space:]]+/','[[:space:]]+',$name);
|
|
if($cnt > 0) $name='[[:space:]]+'.$name;
|
|
$reg_el="(?P<name_{$cnt}>{$name}{$data_type})";
|
|
|
|
if($el['required'] == false) $reg_el.='?';
|
|
$reg[]=$reg_el;
|
|
$cnt++;
|
|
}
|
|
|
|
$reg ="/^[[:space:]]*\n".implode("\n",$reg)."\n[[:space:]]*$/smix";
|
|
|
|
|
|
preg_match($reg,$req,$check);
|
|
if(!empty($check)){
|
|
//check if the query contains subqueries.
|
|
//in this case we execute subqueries and
|
|
//each query is replaced with its result
|
|
preg_match_all('/
|
|
(?P<query_block>
|
|
apiql::query\(
|
|
[[:space:]]*
|
|
(?P<query>
|
|
("[^"]*"|\'[^\']*\')
|
|
)
|
|
[[:space:]]*
|
|
\)
|
|
)/Usmix',$req,$sub_queries);
|
|
|
|
if(isset($sub_queries['query_block']) && !empty($sub_queries['query_block'])){
|
|
foreach($sub_queries['query_block'] as $sq_i=>$sq_str){
|
|
$sq_query =substr($sub_queries['query'][$sq_i],1,-1);
|
|
$sq_res =apiql::query($sq_query);
|
|
$sq_res =(is_string($sq_res))
|
|
?"'".str_replace("'","\\'",$sq_res)."'"
|
|
:apiql::string($sq_res);
|
|
$req=str_replace($sq_str,$sq_res,$req);
|
|
}
|
|
}
|
|
}
|
|
|
|
preg_match($reg,$req,$check);
|
|
if(!empty($check)){
|
|
preg_match($reg,$req,$check);
|
|
$fn_data=array();
|
|
foreach($check as $ck_key=>$ck_val){
|
|
if(strpos($ck_key,'name_') === 0){
|
|
$fn_data[$ck_key] =$ck_val;
|
|
$data_index =substr($ck_key,5);
|
|
if(isset($check['data_'.$data_index])){
|
|
$fn_data[$ck_key] =str_replace($check['data_'.$data_index],'',$fn_data[$ck_key]);
|
|
$fn_data[$ck_key] =trim($fn_data[$ck_key]);
|
|
$fn_data['data_'.$data_index] =$check['data_'.$data_index];
|
|
}
|
|
}
|
|
}
|
|
|
|
$fn_args=array('names'=>array(),'data'=>array());
|
|
foreach($fn_data as $f_key=>$f_value){
|
|
if(strpos($f_key,'name_') === 0){
|
|
$f_cnt=str_replace('name_','',$f_key);
|
|
$fn_args['names'][$f_cnt]=trim($f_value);
|
|
if(isset($fn_data['data_'.$f_cnt])){
|
|
$test_data=trim($fn_data['data_'.$f_cnt]);
|
|
if(preg_match('#^({.*}|\[.*\])$#',$test_data)){
|
|
$test_data=self::JSONdecode($test_data,true);
|
|
}
|
|
else{
|
|
$test_data=self::checkStr($test_data);
|
|
}
|
|
|
|
$fn_args['data'][$f_cnt]=$test_data;
|
|
}
|
|
}
|
|
}
|
|
|
|
//var_dump($fn_args);
|
|
|
|
self::$data=array();
|
|
foreach($fn_args['names'] as $fk=>$name){
|
|
//array_key_exists is better then isset,
|
|
//it is a good solution for not make confusion with null values
|
|
$to_name=(array_key_exists($fk, $fn_args['data']) )
|
|
?$fn_args['data'][$fk]
|
|
:false;
|
|
self::$data[$name]=$to_name;
|
|
}
|
|
|
|
//beforeQuery events
|
|
self::fireEvents('beforeQuery');
|
|
|
|
$send_to_fn=$extra_data;
|
|
array_unshift($send_to_fn,self::$data);
|
|
|
|
$out=call_user_func_array($qry_data['callback'],$send_to_fn);
|
|
|
|
//afterQuery events
|
|
self::fireEvents('afterQuery');
|
|
|
|
return $out;
|
|
}
|
|
}
|
|
}
|
|
|
|
self::trigger_error("apiql query => undefined query");
|
|
return self::reset(true);
|
|
}
|
|
|
|
/*
|
|
* access: private
|
|
*
|
|
* description: convert a string rappresentation of a boolean,number or null
|
|
* value in its effective value
|
|
*
|
|
* vars: str [string] the string to be checked
|
|
*
|
|
* return: its real value and type
|
|
*/
|
|
private static function checkStr($str){
|
|
if(preg_match('#^\'.*\'$#',$str)) $str=substr($str,1,-1);
|
|
|
|
if(is_numeric($str)){
|
|
eval('$check = '.$str.';');
|
|
return $check;
|
|
}
|
|
if(strtolower($str) == 'true') return true;
|
|
if(strtolower($str) == 'false') return false;
|
|
if(strtolower($str) == 'null') return null;
|
|
return $str;
|
|
}
|
|
|
|
|
|
/*
|
|
* access: public
|
|
*
|
|
* description: bind an event function on before or after a query
|
|
*
|
|
* vars: ev [string] the event to register
|
|
* can be 'beforeQuery','afterQuery' or 'error'
|
|
*
|
|
* returns: null
|
|
*/
|
|
public static function addEvent($ev,$fn_name){
|
|
$ev =strtolower((string)$ev);
|
|
$ev =str_replace('query','Query',trim($ev));
|
|
$fn_name=strtolower(trim((string)$fn_name));
|
|
if(isset(self::$events[$ev])){
|
|
self::$events[$ev][]=$fn_name;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* access: public
|
|
*
|
|
* description: remove a specific function from the event specified
|
|
*
|
|
* vars: ev [string] the event from where the function will be deleterd
|
|
* fn_name [string] the function to be removed from.
|
|
*
|
|
* returns: null
|
|
*/
|
|
public static function removeEvent($ev,$fn_name){
|
|
$ev =strtolower((string)$ev);
|
|
$ev =str_replace('query','Query',trim($ev));
|
|
$fn_name=strtolower(trim((string)$ev));
|
|
$key =array_search($fn_name,self::$events[$ev]);
|
|
if($key !== false){
|
|
unset(self::$events[$ev][$key]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* access: public
|
|
*
|
|
* description: remove all function from the events specified
|
|
*
|
|
* vars: [ev1,ev2,ev3] [string] the events from where the functions will be removed
|
|
*
|
|
* returns: null
|
|
*/
|
|
public static function removeEvents(){
|
|
$evs=func_get_args();
|
|
if(!empty($evs)) $evs=array_keys(self::$events);
|
|
|
|
foreach($evs as $ev){
|
|
$ev=strtolower((string)$ev);
|
|
$ev=str_replace('query','Query',trim($ev));
|
|
if(isset(self::$events[$ev]) && !empty(self::$events[$ev])){
|
|
self::$events[$ev]=array();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* access: public
|
|
*
|
|
* description: fires all functions from the events specified
|
|
*
|
|
* vars: [ev1,ev2,ev3] [string] the events from where the functions will be fired
|
|
*
|
|
* returns: null
|
|
*/
|
|
public static function fireEvents(){
|
|
$evs=func_get_args();
|
|
if(empty($evs)) $evs=array_keys(self::$events);
|
|
|
|
|
|
foreach($evs as $ev){
|
|
$ev=strtolower((string)$ev);
|
|
$ev=str_replace('query','Query',trim($ev));
|
|
if(isset(self::$events[$ev]) && !empty(self::$events[$ev])){
|
|
foreach(self::$events[$ev] as $fn){
|
|
if(function_exists($fn))
|
|
$ev_data=call_user_func($fn,self::$data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* access: private
|
|
*
|
|
* description: reset the internal data except the configuration
|
|
*
|
|
* vars: asError [bool] if true, fires the 'error' event
|
|
*
|
|
* returns: false if is called as actiot to execute after an error, or true
|
|
*/
|
|
private static function reset($asError=false){
|
|
self::$syntax = array();
|
|
self::$callback = '';
|
|
self::$isValid = false;
|
|
self::$data = null;
|
|
self::$error = '';
|
|
|
|
if($asError) {
|
|
self::fireEvents('error');
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* access: private
|
|
*
|
|
* description: internal error trigger
|
|
*
|
|
* vars: msg [string] the string to be triggered
|
|
*
|
|
* returns: null
|
|
*/
|
|
private static function trigger_error($msg){
|
|
self::$error=$msg;
|
|
if(self::$config['display_errors'] == true){
|
|
trigger_error($msg,self::$config['error_level']);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* access: public
|
|
*
|
|
* description: convert a not well formed json string in valid php json object
|
|
*
|
|
* vars: json [string] the json string to be analyzed
|
|
* assoc [bool] if true, force as object
|
|
* returns: a well formed json string, valid for PHP
|
|
*/
|
|
public static function JSONdecode($json, $assoc = false){
|
|
$json = str_replace(array("\n","\r"),"",$json);
|
|
|
|
//remove trailing commas and encode in utf8
|
|
$json=preg_replace('/,\s*([\]}])/m', '$1', $json);
|
|
|
|
$json = preg_replace('/([{,])(\s*)([^"]+?)\s*:/','$1"$3":',$json);
|
|
$json = preg_replace('/([{,])"\'([^"]+?)\'":/','$1"$2":',$json);
|
|
|
|
$json = preg_replace('/:(\s*)([^"]+?)\s*([,}])/',':"$2"$3',$json);
|
|
$json = preg_replace('/:"\'([^"]+?)\'"([,}])/',':"$1"$2',$json);
|
|
|
|
$json = preg_replace('/:"(null|true|false|\d+\.\d+|\d+|\.\d+)"([,}])/',':$1$2',$json);
|
|
|
|
return json_decode($json,$assoc);
|
|
}
|
|
|
|
/*
|
|
* access: public
|
|
*
|
|
* description: convert the passed argument as json string
|
|
*
|
|
* vars: obj the value to be converted
|
|
*
|
|
* returns: converted value
|
|
*/
|
|
public static function string($obj=null){
|
|
if(is_object($obj)){
|
|
$out=get_object_vars($obj);
|
|
$out=json_encode($out);
|
|
}
|
|
elseif(is_array($obj)) {$out=json_encode($obj);}
|
|
elseif(is_bool($obj)) {$out=($obj == true)?'true':'false';}
|
|
elseif(is_null($obj)) {$out= 'null';}
|
|
else {$out=(string)$obj;}
|
|
return $out;
|
|
}
|
|
}
|
|
?>
|