Created
August 11, 2016 19:29
-
-
Save dboskovic/da997d36f269d3aecdeb625546d4fea0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Bundles\API; | |
use Exception; | |
use bundles\SQL\ListObj; | |
use e; | |
class Expression { | |
private static $comparison = array('gt' => '>','min' => '>=','max' => '<=','gte' => '>=','in' => '?','lt' => '<','lte' => '<=','ne' => '!=','nin' => '?','range' => '?','eq' => '=','contains' => '?'); | |
private $list; | |
public $expr; | |
public $field; | |
private $rawField; | |
private $asField; | |
private $prettyField; | |
private $raw; | |
private $query; | |
public $having; | |
private $mask; | |
public $min; | |
public $max; | |
private $filters; | |
private $ignoreEmpty = false; | |
private $customOperators = array(); | |
private $fieldModifiers = array(); | |
private $requiredFieldModifiers = array(); | |
private $fieldArguments = array(); | |
public function __construct($expr,$prettyField = false) { | |
// var_dump($prettyField); | |
$this->list = $list; | |
$this->expr = $expr; | |
$this->prettyField = $prettyField; | |
} | |
public function setField($field,$raw = false,$asField = false) { | |
$this->field = $field; | |
$this->rawField = $raw; | |
$this->asField = $asField; | |
} | |
public function ignoreEmpty() { | |
$this->ignoreEmpty = true; | |
} | |
public function setRaw($raw) { | |
$this->raw = $raw; | |
} | |
public function isHaving($having = true) { | |
$this->having = $having; | |
} | |
public function getPreparedField() { | |
if(empty($this->field)) | |
throw new Exception("Trying to execute an expression without a valid comparison field. Please use `setField`."); | |
$fieldArgs = array(); | |
foreach($this->requiredFieldModifiers as $key => $val) { | |
if(!isset($this->expr[$key])) { | |
throw new Exception("You must provide the field modifier `$key` in order to run this query."); | |
} | |
} | |
foreach($this->expr as $key => $value) { | |
// var_dump($key); | |
// dump($this->fieldModifiers); | |
// dump(isset($this->fieldModifiers['date_range'])); | |
if(isset($this->fieldModifiers[$key])) { | |
if(is_callable($this->fieldModifiers[$key])) { | |
$out = call_user_func($this->fieldModifiers[$key],$this,$value); | |
if(!empty($out)) | |
return $out; | |
} | |
} | |
} | |
if(strpos($this->field, '(') === 0 || strpos($this->field, '`') === 0) | |
return $this->field; | |
if($this->rawField) | |
return $this->field; | |
else | |
return "`$this->field`"; | |
} | |
public function selectModifier() { | |
if(empty($this->asField)) | |
return null; | |
if(is_callable($this->asField)) { | |
$query = call_user_func($this->asField,$this); | |
return "$query as `$field`"; | |
} | |
} | |
public function translate() { | |
// direct comparison | |
// var_dump($this->expr); | |
if($this->expr !== null && !is_array($this->expr)) | |
return $this->translateItem('eq',$this->expr); | |
elseif(is_array($this->expr) && is_numeric(key($this->expr))) { | |
$out = array(); | |
foreach($this->expr as $value) { | |
$out[] = $this->translateItem('eq',$value); | |
} | |
if(count($out) > 1) | |
return '('.implode(' OR ', $out).')'; | |
return $out[0]; | |
} elseif(is_array($this->expr)) { | |
$out = array(); | |
foreach($this->expr as $key => $value) { | |
$p = $this->translateItem($key,$value); | |
if(isset($p)) { | |
$out[] = $p; | |
} | |
} | |
if(count($out) > 1) | |
return '('.implode(' AND ', $out).')'; | |
return $out[0]; | |
} | |
return null; | |
} | |
public function setMask($mask) { | |
$this->mask = $mask; | |
} | |
public function setOptions($options) { | |
$this->options = $options; | |
} | |
public function setFilter($filters) { | |
$this->filters = explode('|', $filters); | |
} | |
public function setOperator($op,$eval) { | |
$this->customOperators[$op] = is_null($eval) ? true : $eval; | |
} | |
public function setFieldModifier($op,$eval,$required = false) { | |
$this->fieldModifiers[$op] = is_null($eval) ? true : $eval; | |
if($required) | |
$this->requiredFieldModifiers[$op] = true; | |
} | |
public function setFieldArgument($op,$eval) { | |
$this->fieldArguments[$op] = is_null($eval) ? true : $eval; | |
} | |
public function prepareValue(&$value,$op = false,$i = 0) { | |
// var_dump($value); | |
if(is_array($value)) { | |
$i = 0; | |
foreach($value as &$val) { | |
$this->prepareValue($val,$op,$i); | |
$i++; | |
} | |
return; | |
} | |
if($this->filters) { | |
foreach($this->filters as $filter) { | |
$value = e::$filters->apply($value,$filter); | |
} | |
} | |
if($this->mask) { | |
if(is_callable($this->mask)) { | |
$value = call_user_func($this->mask,$value,$op,$i,$this); | |
} | |
elseif(is_string($value)) { | |
if(!preg_match($this->mask, $value)) { | |
throw new Exception("`$this->prettyField` can only accept values that match this regex: `$this->mask`. You provided: '$value'."); | |
} | |
} | |
} | |
if(is_array($this->options)) { | |
if(!in_array($value, $this->options)) { | |
throw new Exception("`$this->prettyField` can only accept the following values: [".implode(', ', $this->options).']. You provided: "'.$value.'".'); | |
} | |
} | |
} | |
private function translateItem($op, $value) { | |
if(strpos($op, '$') === 0) { | |
$op = substr($op, 1); | |
} | |
// var_dump($op); | |
if(isset($this->fieldModifiers[$op])) | |
return null; | |
if(isset($this->fieldArguments[$op])) | |
return null; | |
if($this->ignoreEmpty && empty($value)) | |
return null; | |
if(!isset(self::$comparison[$op]) && !isset($this->customOperators[$op]) && !isset($this->customOperators['*'])) | |
throw new Exception("`$op` not permitted in expression."); | |
// check for special operator | |
if(isset($this->customOperators[$op]) || isset($this->customOperators['*'])) { | |
if(!isset($this->customOperators[$op])) | |
$cop = '*'; | |
else | |
$cop = $op; | |
// var_dump($cop); | |
if(is_callable($this->customOperators[$cop])) { | |
$out = call_user_func($this->customOperators[$cop],$this,$value,$op); | |
// var_dump($out); | |
if(!empty($out)) | |
return $out; | |
} | |
return null; | |
} | |
$tr = self::$comparison[$op]; | |
$right = ''; | |
$left = $this->getPreparedField(); | |
if($tr === '?') { | |
switch ($op) { | |
case 'in': | |
if(!is_array($value)) | |
throw new Exception("`in` requires value to be array in expression."); | |
$this->prepareValue($value,'in'); | |
$value = e::$sql->quote($value); | |
$right = 'IN('.$value.')'; | |
break; | |
case 'nin': | |
if(!is_array($value)) | |
throw new Exception("`nin` requires value to be array in expression."); | |
$this->prepareValue($value,'nin'); | |
$value = e::$sql->quote($value); | |
$right = 'NOT IN('.$value.')'; | |
break; | |
case 'not': | |
if(!is_array($value)) | |
throw new Exception("`not` requires value to be array in expression."); | |
$this->prepareValue($value,'not'); | |
$value = $this->translate($field,key($value),current($value)); | |
return 'NOT('.$value.')'; | |
break; | |
case 'contains': | |
if(!is_string($value)) | |
throw new Exception("`contains` requires value to be string in expression."); | |
$this->prepareValue($value,'contains'); | |
$value = e::$sql->quote('%'.$value.'%'); | |
return "$left LIKE $value"; | |
break; | |
case 'range': | |
if(!is_array($value) || count($value) !== 2) | |
throw new Exception("`range` requires value to be an array of exactly two items."); | |
$this->prepareValue($value,'range'); | |
$from = e::$sql->quote($value[0]); | |
$to = e::$sql->quote($value[1]); | |
$this->max = array($value[1],'<='); | |
$this->min = array($value[0],'>='); | |
return "($left >= $from && $left <= $to)"; | |
break; | |
default: | |
throw new Exception("Unsupported comparison operator `$op`."); | |
break; | |
} | |
return "$left $right"; | |
} | |
else { | |
if(is_array($value) || is_object($value)) | |
throw new Exception("Simple value required with comparison operator `$op`."); | |
$this->prepareValue($value,$tr); | |
$right = e::$sql->quote($value); | |
if(strpos($tr, '>') !== false) { | |
$this->min = array($value,$tr); | |
} elseif(strpos($tr, '<') !== false) { | |
$this->max = array($value,$tr); | |
} else { | |
$this->min = array($value,$tr); | |
$this->max = array($value,$tr); | |
} | |
return "$left $tr $right"; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Bundles\API; | |
use Exception; | |
use bundles\SQL\ListObj; | |
use e; | |
class Query { | |
private static $compilers = array('$or','$any','$and','$all','$nor','$none'); | |
private $expr; | |
private $collection; | |
public $__expressionCache = array(); | |
public function __construct(Collection $collection) { | |
$this->collection = $collection; | |
} | |
public function compile($query, $parent = false) { | |
// var_dump($query); | |
if(!$parent) | |
$parent = $this; | |
if(!is_array($query)) | |
return; | |
$compilations = array(); | |
foreach($query as $key => $value) { | |
if(is_numeric($key)) { | |
foreach($value as $key => $value) { | |
break; | |
} | |
} | |
if(in_array($key, self::$compilers)) { | |
$parent->__expressionCache[] = new CompilationExpression($this,$key,$value); | |
} else { | |
$v = $this->__evalExpression($key,$value); | |
if($v) | |
$parent->__expressionCache[] = $v; | |
} | |
} | |
// var_dump($parent->__expressionCache ); | |
} | |
public function run() { | |
foreach($this->__expressionCache as $expr) { | |
$eval = $expr->translate(); | |
if(!empty($eval)) { | |
if($expr->having) { | |
$this->collection->__list->having_condition($eval); | |
if($mod = $expr->selectModifier()) { | |
$this->collection->__list->add_select_field($mod,$expr->field); | |
} | |
} else { | |
$this->collection->__list->manual_condition($eval); | |
} | |
} | |
} | |
} | |
public function translate() { | |
$out = array(); | |
foreach($this->__expressionCache as $expr) { | |
$out[] = $expr->translate(); | |
} | |
return $out; | |
} | |
public function __evalExpression($key, $value) { | |
// var_dump('e---',$key,$value,'---'); | |
if($expr = $this->collection->__toExpression($key,$value)) { | |
return $expr; | |
} | |
// dump(method_exists($this->collection, '__customFilter_'.$key)); | |
// dump($key); | |
// @todo remove this completely | |
$this->collection->__applyFilters($key,$value); | |
return false; | |
} | |
} | |
class CompilationExpression { | |
private $__type; | |
public $__expressionCache = array(); | |
public function __construct($query, $op, $expr) { | |
$this->__type = $op; | |
if(count($expr) < 1) { | |
return null; | |
} | |
foreach($expr as $item) { | |
// var_dump($item); | |
$query->compile($item,$this); | |
} | |
} | |
public function translate() { | |
if(count($this->__expressionCache) < 1) { | |
return null; | |
} | |
$compile = array(); | |
foreach($this->__expressionCache as $expr) { | |
if($xp = $expr->translate()) | |
$compile[] = $xp; | |
} | |
if(!count($compile)) | |
return null; | |
switch($this->__type) { | |
case '$or': | |
case '$any': | |
$out = implode(' OR ', $compile); | |
return "($out)"; | |
break; | |
case '$and': | |
case '$all': | |
$out = implode(' AND ', $compile); | |
return "($out)"; | |
break; | |
case '$nor': | |
case '$none': | |
foreach($compile as &$item) { | |
$item = "NOT($item)"; | |
} | |
$out = implode(' AND ', $compile); | |
return "($out)"; | |
break; | |
} | |
return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment