Вход Регистрация
Файл: 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;
}
Онлайн: 2
Реклама