PHP Класс для работы с плейсхолдерами в SQL

vasinsky

Новичок
ну соб-но вчера тема поднялась, просили показать какие навыки
класс не претендует на звание лучшего, и тем более я не навязываю использовать его в своих целях

убедительная просьба - не орать с пеной из-за рта о создании очередного велосипеда
этот топик появился в ходе беседы http://phpclub.ru/talk/threads/Получение-html-после-выполнения-клиентских-скриптов.77308/page-3#post-695845

написан на коленке, урывками по среди рабочего времени.

прошу указать на недачёты, не только в проектировании класса, но и возможно более гибкие алгоритмы для реализации поставленной задачи

данная реализация разбита на 2 класса - работа с типами и непосредственный парсинг sql строки

Последовательность плейсхолдеров в строке и последовательность в массиве с данными - не важна.

PHP:
/**
* класс типизации входных данных
*/
class Types{
    /**
    * Описание констант плейсхолдеров
    */
    const I = 'i:';   //int
    const F = 'f:'; //float
    const S = 's:'; //string
    const B = 'b:'; //bool
    const N = 'n:'; //null

    /**
    *  array
    *  собирает обработанные входные данные
    */
    protected $params;

    public function __construct(){

    }

    /**
    * Приводит данные к типу int
    * @param int,float
    * @return int
    */
    private function _int($data){
        return intval($data);
    }
    /**
    * Приводит данные к типу float
    * @param int,float
    * @return float
    */
   private function _float($data){
        return round(floatval($data), 2);
    }
    /**
    * Приводит данные к типу string + экранирует спец. символы
    * @param int,float,string
    * @return string
    */
    private function _string($data){
        //addsleshes здесь смотрится неуклюже
        //более правильней, наверно - формировать $data с данными - если строка - то ->real_escape_string()
        //и пихать real_escape_string сюда - не вижу смысла
        return (string)addslashes($data);
    }
    /**
    * Приводит данные к типу bool
    * @param int,float,string
    * @return bool
    */
    private function _bool($data){
        return (bool)$data;
    }
    /**
    * подставляет null вместо значения
    * я не понял зачем этот плейсхолдер нужен, когда null можно подставить в массив с данными
    * @param void
    * @return NULL as string
    */
    protected function _null(){
        return 'null';
    }

    /**
    *  Приводит к типу входные данные, согласно константам плейсхолдеров
    */
    protected function setType($mark_plhdr, $data){
        if($mark_plhdr == self::I)
          $this->params[] = $this->_int($data);
        elseif($mark_plhdr == self::F)
          $this->params[] = $this->_float($data);
        elseif($mark_plhdr == self::S)
          $this->params[] = "'".$this->_string($data)."'";
        elseif($mark_plhdr == self::B)
          $this->params[] = $this->_bool($data);
        elseif($mark_plhdr == self::N)
          $this->params[] = $this->_null();
        else
          throw new Exception('Попытка использования неизвестного плейсхолдера: '.$mark_plhdr);
    }
}
PHP:
/**
* класс обработки SQL запросов с использование плейсхолдеров
*/
class Stmt extends Types{
    protected $marks = array();

    public function __construct(){
        parent::__construct();
    }

    /**
    * Парсер плейсхолдеров в SQL запросе
    * формирует свойства объекта, заполняет $this->marks - плейсхолдеры вместе с метками, $this->params - обработанными данными
    * @param sql string (валидный sql запрос)
    * @param data array  (входные данные в виде ассоц массива, с ключами по меткам плейсхолдеров)
    */
    private function parseSql($sql, $data){
        if(!is_string($sql)){
            throw new Exception('SQL запрос должен быть предоставлен в виде строки');
            return false;
        }
        else{
            preg_match_all("#([a-z]{1}\:)#s", $sql, $plhdr);
            preg_match_all("#\:([a-z0-1]{1,})#s", $sql, $plhdr_data);
  
  
            if(!$plhdr[1] or empty($plhdr[1]))
                throw new Exception('Ошибка определения плейсходера(ов)');
            elseif(count($plhdr[1]) != count($plhdr_data[1]))
                throw new Exception('Ошибка определения пары: плейсхолдер - данные');
            else{
                foreach($plhdr[1] as $k=>$v){
          
                    $this->marks[] = $v.$plhdr_data[1][$k];
          
                    if(!isset($data[$plhdr_data[1][$k]]))
                        throw new Exception('Не указанно значение параметра : '.$plhdr_data[1][$k].', при использовании '.$v.$plhdr_data[1][$k]);
                    else
                        $this->setType($v, $data[$plhdr_data[1][$k]]);
                }
      
            }
        }

    }
    /**
    * конечный этап работы с плейсхолдерами в SQL
    * @param sql string (валидный sql запрос)
    * @param data array  (входные данные в виде ассоц массива, с ключами по меткам плейсхолдеров)
    * @return string
    *  пускай название с толку не сбивает - это сокращение от placeholder, его всегда можно сменить на более красивое
    */
    public function plhdr($sql, $data){
        $this->parseSql($sql, $data);

        if(!empty($this->marks)){
            foreach($this->marks as $k=>$m){
                $sql = strtr($sql, array($m => $this->params[$k]));
            }
            return $sql;
        }
        //здесь впринципе можно поругаться - что запрос не был обработан и вернулся в исходном виде
        return $sql;
    }
}
использование:

PHP:
$stmt = new Stmt;

$data = array(
    'price' => 10.1291231,
    'count' => 25,
    'text'=>"as'fsfas"
);

echo $stmt->plhdr("SELECT good_name, price, count FROM bla WHERE price BETWEEN (f:price AND f:price * i:count) AND count > i:count AND `text` = s:text" , $data);
результат:
Код:
SELECT good_name, price, count FROM bla WHERE price BETWEEN (10.13 AND 10.13 * 25) AND count > 25 AND `text` = 'as\'fsfas'
 
Последнее редактирование:

vasinsky

Новичок
вообще - было бы хорошо использовать статический метод - для обработки sql, но т.к. использую свойства для хранения данных - от объекта отказаться не смог. мозгов не хватило.
 

riff

Новичок
2 imho:
$stmt = new Stmt;
echo $stmt->plhdr("SELECT...
Надо перевести на статику. Каждый раз new, new, new - это убивает.

if(!is_string($sql)){
throw new Exception('SQL запрос должен быть предоставлен в виде строки');
}
else{ //<---------
...
}
Попробуй отвыкнуть от излишнего ветвления. Уровнем выше и так всё заканчивается.

Ну и ради интересу чуть сократил твой код:
PHP:
    protected function setType($mark_plhdr, $data)
    {
        if ($mark_plhdr == self::I) return $this->_int($data);
        elseif ($mark_plhdr == self::F) return $this->_float($data);
        //ну и т.д.
    }
PHP:
    public function parseSql($sql, $data)
    {
        if (empty($sql)) return $sql;

        preg_match_all("~([a-z]):([a-z][a-zA-Z0-1_]*)~", $sql, $plhdr);
        if (empty($plhdr)) return $sql;
        $replace = array();

        foreach ($plhdr[0] as $k => $v)
        {
            //добавить проверку isset($data[]), стёр случайно
            $replace[$v] = $this->setType($plhdr[1][$k], $data[$plhdr[2][$k]]);
        }
        return strtr($sql, $replace);
    }

    public function plhdr($sql, $data) //забыл сказать, её можно удалить
    {
        $sql = $this->parseSql($sql, $data);
        return $sql;
    }
parseSql можно наверно ещё облагородить, но времени увы нет.

addslashes на твоей совести.
 
Последнее редактирование:

Redjik

Джедай-мастер
прошу указать на недачёты, не только в проектировании класса, но и возможно более гибкие алгоритмы для реализации поставленной задачи
открой для себя препареды...

читать все лень, думаю Никита и Рома оставят ссылки на свои реализации
 

vasinsky

Новичок
принято.
но алгоритм parseSql() затуманен, нету уже той прозрачности

смысл plhdr() был как раз в конечном этапе
задача parseSql() - была распарсить

я спец. эти алгоритмы логически отделил друг от друга

Ну ради интересу чуть сократил твой код:
ну по сути - обработка плейсхолдера может быть намного сложнее, по этому - смотря вперёд - я и разделил всё на методы

я уверен, есть на этом свете человек, который скажет - вынести эти методы отдельно в свои классы и использовать интерфейс для них
 

vasinsky

Новичок
открой для себя препареды...

читать все лень,
ну если лень - вообще бы не начинал читать.

чё за препареды? мне дворовый язык не понятен, не шатаюсь по дворам.
хоть ссылку бы кинул.
 

vasinsky

Новичок
я на личности не переходил - аккуратнее
я и так аккуратно это сделал. с учётом твоего поста.
я выше написал - ещё раз скажу - этот велосипед меня вчера попросили написать твои местные знакомые.
 

riff

Новичок
Redjik, С этим комментарием даже я не согласен.
открой для себя препареды..
На много это
$stmt = $dbh->prepare("SELECT * FROM users WHERE USERNAME = ? AND PASSWORD = ?");
$stmt->execute(array($username, $password));
офигительней этого:
$stmt->plhdr("SE...", array(...));
?

Почему в столь же категоричной форме ты не написал в теме Фаната, мол хватит заниматься чушью, на дворе препаред от пдо?
 

Redjik

Джедай-мастер
Потому, что мы и так много с ним обсуждали класс вне форума, мне нечего добавить.
Вот, что мне нравится https://github.com/FountainDb/pdoshechka, кроме дурацкого названия (прости hell0w0rd =)))
 

riff

Новичок
Ладно, давайте сразу прекратим на этом. А то сейчас превратим тему в то, во что тузик превратил грелку. Хотя "все всё понимают". :)
 

vasinsky

Новичок
Надо перевести на статику. Каждый раз new, new, new - это убивает.
с этим и я полностью согласен,но

свойство protected $marks = array(); относиться именно к классу Stmt, как от него отказаться - я пока не глядел
а то что в статике преимущество - так я полностью согласен

а применение а такого рода классов я не вижу, есть mysqli и PDO, где давно уже всё придумано
писать обёртку для обёртки - не камильфо
 

AmdY

Пью пиво
Команда форума
приехали.
addslashes не только неуместен, но и совсем не правильно с учётом повсеместного utf http://phpfaq.ru/slashes
 

vasinsky

Новичок
я по этому поводу не спорю, о чём и написал в комменте..

остальное -как?
 

WMix

герр M:)ller
Партнер клуба
Код:
в setType($mark_plhdr, $data) читабельней использовать switch
#([a-z]{1}\:)#s это ты погорячился используются то всего 5 букв
#\:([a-z0-1]{1,})#s а тут про подчеркиванье камелкейс забыл
class Stmt extends Types разрыв мозга. не по русски как-то
public function __construct(){parent::__construct(); } беcполезный метод
if(!$plhdr[1] or empty($plhdr[1])) это возможный notice нет?
floatы мне бывает маловато 2 значения после запятой
Stmt, plhdr любитель еврита ))
но все мелкие недочеты можно закрыть глаза. задачка решена на уровне чистого string без базы, мне нравится. нужно внимательней читать, а не так глазами пробежаться
 
Сверху