Файл: sys/library/goDB/Helpers/Templater.php
Строк: 477
<?php
/**
* @package goDB
*/
namespace goDBHelpers;
use goDBCompat;
use goDBExceptionsDataMuch;
use goDBExceptionsDataNotEnough;
use goDBExceptionsDataNamed;
use goDBExceptionsDataInvalidFormat;
use goDBExceptionsUnknownPlaceholder;
use goDBExceptionsMixedPlaceholder;
/**
* The query templating system
*
* @author Oleg Grigoriev <go.vasac@gmail.com>
*/
class Templater
{
/**
* The constructor
*
* @param goDBHelpersConnector $connector
* a database connector (a connection must be established)
* @param string $pattern
* a query pattern
* @param array $data
* an incoming data for the pattern
* @param string $prefix
* a prefix for tables
*/
public function __construct(Connector $connector, $pattern, $data, $prefix)
{
$this->implementation = $connector->getImplementation();
$this->connection = $connector->getConnection();
$this->pattern = $pattern;
$this->data = $data ?: array();
$this->prefix = $prefix;
}
/**
* The query templating
*
* @return string
* the result query
* @throws goDBExceptionsTemplater
*/
public function parse()
{
if ($this->query !== null) {
return $this->query;
}
$query = preg_replace_callback('~{(.*?)}~', array($this, 'tableClb'), $this->pattern);
$pattern = '~?([a-z?-]+)?(:([a-z0-9_-]*))?;?~i';
$callback = array($this, 'placeholderClb');
$query = preg_replace_callback($pattern, $callback, $query);
if ((!$this->named) && (count($this->data) > $this->counter)) {
if (($this->counter > 0) || (isset($this->data[0]))) {
throw new DataMuch(count($this->data), $this->counter);
}
}
$this->query = $query;
return $this->query;
}
/**
* Returns the result query
*
* @return string
*/
public function getQuery()
{
return $this->query;
}
/**
* Replaces "{table}" to a table name
*
* @param array $matches
* @return string
*/
private function tableClb($matches)
{
return $this->implementation->reprTable($this->connection, $this->prefix.$matches[1]);
}
/**
* Replaces a next placeholder in the pattern
*
* @param array $matches
* @return string
* @throws goDBExceptionsTemplater
*/
protected function placeholderClb($matches)
{
$placeholder = isset($matches[1]) ? $matches[1] : '';
if (isset($matches[3])) {
$name = $matches[3];
if (empty($name)) {
/* There is a named placeholder without name ("?set:") */
throw new UnknownPlaceholder($matches[0]);
}
} else {
$name = null;
if ($placeholder == '?') { // "??" for question mark
return '?';
}
}
if ($name) {
if ($this->counter == 0) {
$this->named = true;
} elseif (!$this->named) {
/* There is a named placeholder although already used regular */
throw new MixedPlaceholder($matches[0]);
}
if (!array_key_exists($name, $this->data)) {
throw new DataNamed($name);
}
$value = $this->data[$name];
} elseif ($this->named) {
/* There is a regular placeholder although already used named */
throw new MixedPlaceholder($matches[0]);
} else {
if (!array_key_exists($this->counter, $this->data)) {
/* Data for regular placeholders is ended */
throw new DataNotEnough(count($this->data), $this->counter);
}
$value = $this->data[$this->counter];
}
$this->counter++;
$parser = new ParserPH($placeholder);
$type = $parser->getType();
$modifiers = $parser->getModifiers();
$method = 'replacement'.strtoupper($type);
return $this->$method($value, $modifiers);
}
/**
* Converts a scalar value in conformity with the modifiers list
*
* @param mixed $value
* @param array $modifiers
* @return string
*/
private function valueModification($value, array $modifiers)
{
if (is_null($value)) {
if ($modifiers['n'] || Compat::getOpt('types')) {
return $this->implementation->reprNULL($this->connection);
}
}
if ($modifiers['i']) {
return $this->implementation->reprInt($this->connection, $value);
} elseif ($modifiers['f']) {
return $this->implementation->reprFloat($this->connection, $value);
} elseif ($modifiers['b']) {
return $this->implementation->reprBool($this->connection, $value);
}
if (Compat::getOpt('types')) {
$type = gettype($value);
if ($type === 'integer') {
return $this->implementation->reprInt($this->connection, $value);
} elseif ($type === 'double') {
return $this->implementation->reprFloat($this->connection, $value);
} elseif ($type === 'boolean') {
return $this->implementation->reprBool($this->connection, $value);
}
}
return $this->implementation->reprString($this->connection, $value);
}
/**
* ?, ?scalar
*
* @param mixed $value
* @param array $modifiers
* @return string
*/
protected function replacement($value, array $modifiers)
{
if (is_array($value)) {
throw new DataInvalidFormat('', 'required scalar given');
}
return $this->valueModification($value, $modifiers);
}
/**
* ?string
* @param mixed $value
* @param array $modifiers
* @throws DataInvalidFormat
* @return string
*/
protected function replacementSTRING($value, array $modifiers)
{
if (is_array($value)) {
throw new DataInvalidFormat('', 'required scalar given');
}
if (($modifiers['n'] || Compat::getOpt('types')) && is_null($value)) {
return $this->implementation->reprNULL($this->connection);
}
return $this->implementation->reprString($this->connection, $value);
}
/**
* ?l, ?list
*
* @param array $value
* @param array $modifiers
* @return string
*/
protected function replacementL($value, array $modifiers)
{
if (!is_array($value)) {
throw new DataInvalidFormat('list', 'required array (list of values)');
}
$values = array();
foreach ($value as $k => $element) {
if (is_array($element)) {
throw new DataInvalidFormat('list', 'required scalar in item #'.$k);
}
$values[] = $this->valueModification($element, $modifiers);
}
return implode(', ', $values);
}
/**
* ?s, ?set
*
* @param array $value
* @param array $modifiers
* @return string
*/
protected function replacementS($value, array $modifiers)
{
if (!is_array($value)) {
throw new DataInvalidFormat('set', 'required array (column => value)');
}
$set = array();
foreach ($value as $col => $element) {
$key = $this->implementation->reprCol($this->connection, $col);
if (is_array($element)) {
if (empty($element)) {
$element = 'NULL';
} else {
$element = $this->replacementC($element, $modifiers);
}
} else {
if (is_int($element)) {
$element = $this->implementation->reprInt($this->connection, $element);
} else {
$element = $this->valueModification($element, $modifiers);
}
}
$set[] = $key.'='.$element;
}
return implode(', ', $set);
}
/**
* ?v, ?values
*
* @param array $value
* @param array $modifiers
* @return string
*/
private function replacementV($value, array $modifiers)
{
if (!is_array($value)) {
throw new DataInvalidFormat('values', 'required array of arrays');
}
$values = array();
foreach ($value as $v) {
$values[] = '('.$this->replacementL($v, $modifiers).')';
}
return implode(', ', $values);
}
/**
* ?t, ?table
*
* @param array $value
* @param array $modifiers
* @return string
*/
private function replacementT($value, array $modifiers)
{
if (!is_array($value)) {
return $this->implementation->reprTable($this->connection, $this->prefix.$value);
}
if (isset($value[0])) {
$chain = $value;
} elseif (isset($value['table'])) {
if (is_array($value['table'])) {
$chain = $value['table'];
} else {
$chain = array($value['table']);
}
if (isset($value['db'])) {
array_unshift($chain, $value['db']);
}
} elseif (!isset($value['table'])) {
throw new DataInvalidFormat('t', 'required `table` field');
}
$lastIdx = count($chain) - 1;
$chain[$lastIdx] = $this->prefix.$chain[$lastIdx];
$result = $this->implementation->reprChainFields($this->connection, $chain);
if (isset($value['as'])) {
$result .= ' AS '.$this->implementation->reprCol($this->connection, $value['as']);
}
return $result;
}
/**
* ?c, ?col
*
* @param array $value
* @param array $modifiers
* @return string
*/
private function replacementC($value, array $modifiers)
{
if (!is_array($value)) {
return $this->implementation->reprCol($this->connection, $value);
}
if (isset($value[0])) {
if ($this->prefix !== null) {
$t = count($value) - 2;
if (isset($value[$t])) {
$value[$t] = $this->prefix . $value[$t];
}
}
return $this->implementation->reprChainFields($this->connection, $value);
}
if (isset($value['col'])) {
$chain = array();
foreach (array('db', 'table', 'col') as $f) {
if (isset($value[$f])) {
if (is_array($value[$f])) {
$chain = array_merge($chain, $value[$f]);
} else {
$chain[] = $value[$f];
}
}
}
if ($this->prefix !== null) {
$t = count($chain) - 2;
if (isset($chain[$t])) {
$chain[$t ] = $this->prefix.$chain[$t];
}
}
$result = $this->implementation->reprChainFields($this->connection, $chain);
} elseif (isset($value['value'])) {
if (is_int($value['value'])) {
$result = $this->implementation->reprInt($this->connection, $value['value']);
} else {
$result = $this->implementation->reprString($this->connection, $value['value']);
}
if (array_key_exists('col', $value) && isset($value['func'])) {
$result = '';
} else {
$value['value'] = null;
}
} elseif (isset($value['func'])) {
$result = '';
} else {
throw new DataInvalidFormat('col', 'required `col`, `value` or `func` field');
}
if (isset($value['func'])) {
$result = $value['func'].'('.$result.')';
}
if (isset($value['value'])) {
$result .= (($value['value'] > 0) ? '+' : '').(int)$value['value'];
}
if (isset($value['as'])) {
$result .= ' AS '.$this->implementation->reprCol($this->connection, $value['as']);
}
return $result;
}
/**
* ?cols
*
* @param array $value
* @param array $modifiers
* @return string
*/
private function replacementXC($value, array $modifiers)
{
if (!is_array($value)) {
if ($value === true) {
return '*';
}
return $this->replacementC($value, $modifiers);
}
if (empty($value)) {
return '*';
}
$cols = array();
foreach ($value as $col) {
$cols[] = $this->replacementC($col, $modifiers);
}
return implode(',', $cols);
}
/**
* ?e, ?escape
*
* @param array $value
* @param array $modifiers
* @return string
*/
private function replacementE($value, array $modifiers)
{
if (is_array($value)) {
throw new DataInvalidFormat('escape', 'required string');
}
return $this->implementation->escapeString($this->connection, $value);
}
/**
* ?q, ?query
*
* @param array $value
* @param array $modifiers
* @return string
*/
private function replacementQ($value, array $modifiers)
{
if (is_array($value)) {
throw new DataInvalidFormat('query', 'required string');
}
return $value;
}
/**
* ?w, ?where
*
* @param mixed $value
* @param array $modifiers
* @return string
*/
private function replacementW($value, array $modifiers)
{
return $this->whereGroup($value, $modifiers);
}
/**
* ?o, ?order
*
* @param mixed $value
* @param array $modifiers
* @return string
*/
private function replacementO($value, array $modifiers)
{
if (!is_array($value)) {
return $this->replacementC($value, $modifiers).' ASC';
}
$stats = array();
foreach ($value as $k => $v) {
if (is_int($k)) {
$c = $v;
$s = 'ASC';
} else {
$c = $k;
$s = $v ? 'ASC' : 'DESC';
}
$stats[] = $this->replacementC($c, $modifiers).' '.$s;
}
return implode(',', $stats);
}
/**
* @param mixed $value
* @param array $modifiers
* @param string $sep [optional]
* @return string
*/
private function whereGroup($value, array $modifiers, $sep = 'AND')
{
if (!is_array($value)) {
return ($value !== false) ? '1=1' : '1=0';
}
$stats = array();
foreach ($value as $k => $v) {
$col = $this->replacementC($k, $modifiers);
if (is_array($v)) {
if (empty($v)) {
$stats[] = '1=0';
continue;
}
if (isset($v[0])) {
$opts = array();
$hasNull = false;
foreach ($v as $opt) {
if (is_int($opt)) {
$opts[] = $opt;
} elseif (is_null($opt)) {
$hasNull = true;
} else {
$opts[] = $this->replacement($opt, $modifiers);
}
}
$stat = $col.' IN ('.implode(',', $opts).')';
if ($hasNull) {
$stat = '('.$stat.' OR '.$col.' IS NULL)';
}
} elseif (isset($v['group'])) {
$sepG = isset($v['sep']) ? $v['sep'] : 'AND';
$stat = '('.$this->whereGroup($v['group'], $modifiers, $sepG).')';
} else {
$op = isset($v['op']) ? $v['op'] : '=';
$stat = $col.$op.$this->replacementC($v, $modifiers);
}
} elseif ($v === null) {
$stat = $col.' IS NULL';
} elseif ($v === true) {
$stat = $col.' IS NOT NULL';
} elseif (is_int($v)) {
$stat = $col.'='.$v;
} else {
$stat = $col.'='.$this->replacement($v, $modifiers);
}
$stats[] = $stat;
}
if (empty($stats)) {
return '1=1';
}
return implode(' '.$sep.' ', $stats);
}
/**
* @var goDBImplementationsBase
*/
protected $implementation;
/**
* @var mixed
*/
protected $connection;
/**
* @var string
*/
protected $pattern;
/**
* @var array
*/
protected $data;
/**
* @var string
*/
protected $prefix;
/**
* @var string
*/
protected $query;
/**
* @var int
*/
protected $counter = 0;
/**
* @var bool
*/
protected $named = false;
}