Skip to content

Instantly share code, notes, and snippets.

@dboskovic
Created August 11, 2016 19:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dboskovic/da997d36f269d3aecdeb625546d4fea0 to your computer and use it in GitHub Desktop.
Save dboskovic/da997d36f269d3aecdeb625546d4fea0 to your computer and use it in GitHub Desktop.
<?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";
}
}
}
<?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