SQL-запрос на PHP (Версия 0.2) -7

Внес изменения в свою реализацию класса для генерации SQL запросов по сравнению с прошлой версией. Однако прежде чем писать о них, хотелось сразу прояснить некоторые вопросы которые в первом посте остались, на мой взгляд, не раскрыты:
- Это не очередной QueryBuilder. Хотя синтаксис и выглядит похоже, но по сути работает это по-другому. QueryBuilder обычно выполняется в Runtime. Мой же код предварительно нужно компилировать и на выполнение запускать уже откомпилированную версию. В частности код с картинки будет скомпилирован в следующий код PHP:
 
 if (!isset($__query58)) { $__query58 = new \ML\SQL\Query( '0cfd84f430c39bb567ef7cd28bf36054', array( 'server' => '***', 'database' => '***', 'user' => '***', 'pass' => '***', 'prefix' => 'ms-', 'codepage' => 'utf8' ) ); } $__query58->sql = ' SELECT `profile`.`iduser` AS `profile.iduser`,`user`.`name` AS `user.name` FROM `ms-Users-Profile` AS `profile`LEFT OUTER JOIN `ms-Users-User` AS `user` ON `user`.`id`=`profile`.`iduser` WHERE (`profile`.`net`=\'VK\' OR `profile`.`net`=\'OK\') AND `user`.`sex`=\'M\''; $rows = $__query58->rows();
 При использовании переменных в качестве имен полей или значений они будут вставлены в запрос через вызовы специальных функций, которые исключат SQL-инъекцию.
 
 
- Для чего это нужно: я планирую сделать свой ORM (само собой с блекджеком и шлюхами) и PHPSQL будет являться абстрактным слоем для работы с БД. Планируется писать запросы в виде:
 
 select(id)->from(Users\User)->where(sex == 'M');
 Который затем будет компилировать в PHP код с промежуточной компиляцией в PHPSQL.
 
 
- Нет, такой подход с компиляцией файлов не будет тормозить работу. Достаточно сделать это один раз при первом обращении к файлу, а затем закешировать скомпилированный вариант и в дальнейшем работать уже с ним. Также это можно делать один раз при установке/обновлении движка через консоль. В этом случае нам вообще неважно сколько времени займет компиляция, главное чтобы итоговый файл работал быстро.
 
 
- Да, такой подход действительно может создавать проблемы в отладке. Потому что из-за сгенерированного кода при ошибке в коде номер строки ошибки будет соответствовать новому скомпилированному файлу, а не тому, в котором вы этот код набирали.На текущий момент я пока не придумал как это обойти. Если есть какие-то идеи — озвучьте их.
 
 
- Да, я в курсе что «вместо изобретения своего велосипеда лучше бы использовал что-то крутое, типа Doctrine|Eloquent|другой вариант». Но я пошел своим путем потому что 1) мне это интересно 2) мне это нравится.
 
 
- Почему не выложил на гибхаб: потому что данный класс использует функции из других моих библиотек, которые в свою очередь требуют еще библиотек и в общем это все занимает 17 мб исходных текстов, по которым нет справки. При этом из некоторых библиотек используется одна/две простенькие функции. На текущий момент пока не вижу смысла делать рефакторинг чтобы вынести этот функционал в отдельную небольшую папку для общего доступа. Будет интерес — выложу.
Ну а теперь перейдем к изменениям:
- Самое основное нововведение — в новой редакции все константные строки интерпретируются как имена полей. Т.е. теперь вместо field(net) == 'VK' можно писать net == 'VK'. Для имен полей с символом тире в имени можно использовать оператор исполнения в обратных ковычках: `net-soc`== 'VK'. Функция field осталась, её можно использовать если имя поля, к примеру, находится в переменной: field($fieldname). 
 
 
- Исчезли функции вида _And, _Or. Теперь в условии нужно использовать вместо них операторы && и ||. Т.е. раньше было _Or(field(net)==value('VK'),field(net)==value('OK')), в новой редакции net=='VK' || net=='OK'
 
 
- Добавилась поддержка JOIN запросов (на стартовой картинке как раз пример с ним).
 
 
- Добавлен низкоуровневый метод Query, который позволяет создавать любые запросы,
 которые нельзя сделать с помощью специализированных функций. По сути это просто обычное формирование запроса с помощью конкатенации строк. Однако в методе можно использовать специальные функции, которые позволяют исключить SQL-инъекции.
 
 
- Расширилась функция field. Теперь можно написать в виде field([<имя псевдонима таблицы>,]<имя поля>)[->as(<имя псевдонима поля>)]
 
 
- Поддержка сортировки, группировки, условий агрегации
Для тех, кому интересно, тестовый полигон для компиляции кода находится по тому же адресу. Если найдете какую-то ошибку, пишите в комментариях. Пока это версия 0.2 так что ошибки весьма вероятны.
UPD: Судя по всему 90% комментаторов осилили только заголовок и картинку под ним и тут же начали писать комментарий в стиле «ваш велосипед гавно, лучше пишите на xxx/yyy/zzz». Хотя специально для таких был написан пункт 5.
UPD2: Добавил команды INSERT, UPDATE, DELETE. Также в команду SELECT добавил offset() и limit(). Так что теперь можно свою ORM делать.
UPD2.1: Добавил метод COUNT в SELECT. Если в нем указать переменную, то в неё вернется общее количество записей без учета команды LIMIT. Сам запрос тоже выполнится. Это для пагинаторов сделано.
Комментарии (58)
 - Akuma24.04.2017 09:29+4- Как по мне, так велосипед с первой статьи не изменился. Единственный «плюс», который я вижу: переменные биндятся сразу на месте, а не в конце списком параметров. Даже если опустить удобство биндинга в конце, ваш вариант проигрывает тупо из-за неудобства написания SQL в вашем синтаксисе. 
 
 Если уж так хочется достичь того же самого, сделайте проще:
 ML(«SELECT id, name FROM table WHERE name = {$name}»);
 
 Парсите такие строки и так же автоматически подменяйте переменные на безопасный биндинг параметров. Таким образом вы убьете всех зайцев разом: стандартный синтаксис, нормальная подсветка синтаксиса, IDE ни на что не ругается и вы получаете свой профит в виде биндига на месте. - shasoft24.04.2017 10:08-3- Самый первый вариант таким и был. В этом случае гораздо сложнее парсинг выражения. И каждый JOIN усложняет этот парсинг на порядок.  - keksmen24.04.2017 10:32+1- На самом деле, нет. При правильном построении грамматики запроса последовательный разбор становится достаточно несложной задачей.  - shasoft24.04.2017 21:22- К примеру такой простой запрос: 
 - ML(«SELECT id, name FROM table WHERE {$name}={$fieldname}»); каким образом понять где у нас имя поля, а где имя значения? Т.е. уже нужны какие-то модификаторы для обозначения этого. Добавить к этому куски запроса, которые вставляются по запросу — уже получается не так просто.
 В теории — да, действительно просто. Поэтому и делал так. Как говорится, дьявол, он в деталях. И в данном случае этих деталей много. В результате требуется полноценный разборщик строки на лексемы и гораздо проще использовать парсер PHP. - YemSalat25.04.2017 11:48-1- каким образом понять где у нас имя поля, а где имя значения? 
 Да, придется разбирать SQL выражение. Но по-моему это был бы гораздо более интересный и полезный проект, т.к. то что вы предлагаете, как уже замечено — уже 1000 раз реализовано.
 
 
  - Akuma24.04.2017 11:04+1- Ну а как вы хотели? 
 
 Сейчас вы пошли легким для себя, но бессмысленным для окружающих путем. Для «домашнего» использования это может и прокатит, а вот для массового — увы, нет.
 
 
 - AterCattus24.04.2017 10:36+6- Оба поста выглядят как 
 «Я вот придумал штуку, которую уже много раз делали, но я вам свой вариант все-равно не покажу.»
 - basili424.04.2017 10:46-1- Мой же код предварительно нужно компилировать и на выполнение запускать уже откомпилированную версию - Я вот чего не пойму как будет предварительно компилироваться запрос select * from pages p where p.page_id=(номер страницы переданный в запросе к странице);? 
 - И какой смысл в предворительной компиляции запроса в строку, если сам запрос и его план можно кешировать на стороне сервера? 
 - QuickJoey24.04.2017 11:08- И эти люди запрещают использовать хранимые процедуры в качестве application server. Глядя на картинки становится невыносимо печально, а ведь это простейший запрос. Чуть сложнее логика, не дай бог с временными таблицами в запросе и всё, в этой каше разобраться будет нереально. Попробуйте хранимые процедуры, вам понравится. - michael_vostrikov24.04.2017 12:16- У автора просто QueryBuilder не очень хороший, да и не совсем Builder. С нормальным подходом будет как-то так: 
 - $rows = Profile::find() ->joinWith('user') ->select(['profile.user_id', 'user.name']) ->where(['user.sex' => 'M']) ->andWhere(['profile.net' => ['VK', 'OK']]) ->asArray() ->all();
 - Вместо небольших временных таблиц в языке есть массивы, да и работа с большими не сильно отличается от обычных.  - QuickJoey24.04.2017 12:31- Я немного про другое. Вместо универсального построителя любых запросов, использовать процедуры под конкретную задачу. Процедура называется, например, userprofile_get, и на вход ждёт два параметра _sex и _net. Тогда вызов будет что-то типа: 
 - call userprofile_get (_sex='M', _net='VK;OK');
 - а внутри этой процедуры может быть какой угодно сложный запрос, который оставит код на PHP/Ruby/c++ лёгким. - michael_vostrikov24.04.2017 12:48+2- Проблема в том, что его надо поддерживать. А поддерживать код на обычном языке программирования гораздо проще. 
 
 
  - Zhuravljov24.04.2017 12:28+2- Кто запрещает то? Если хранимая процедура дает серьезный выигрыш по совокупности факторов производительности и сопровождаемости, то почему бы и нет. 
 - Просто их сложнее поддерживать в актуальном состоянии. Я про условия командной разработки, где актуальность кода гарантирует VCS, а структуры БД — миграции. И тут получается, что хранимая процедура — не рыба не мясо, не код и не структура. Изменения хранимок в миграциях держать неудобно, потому что это код, и неотъемлемая часть общей логики, и было бы неплохо иметь возможность с помощью Git-а посмотреть историю изменений в этом самом коде. Но и в sql-файлах их держать тоже не так удобно, потому что нет гарантий, что хранимка скомпилится, если миграциями не обновили структуру до совместимого с ней состояния. 
 - Но, если в команде выработаны общие подходы по работе с хранимками, то нет проблем. 
 
- AlexLeonov24.04.2017 11:09-1- Все писатели подобных QueryBuilder-ов упорно не понимают простую истину: чем сложнее запрос, тем проще написать его на SQL, без всяких QueryBuilder-ов. 
 
 Делайте проще — и в этом будет смысл. Постараетесь учесть все нюансы SQL — и вы изобретете заново SQL, но с дурными синтаксисом и кучей ограничений, запутаетесь и сделаете бессмысленный комбайн, который всё равно работает от силы в половине случаев. - Zhuravljov24.04.2017 11:23+2- Спорное утверждение. Все сильно зависит от ситуации. Если ваш запрос сложный но монолитный, то да, проще на чистом SQL. А если наличие каких-то джоинов и условий зависит от внешних факторов, то без QueryBuilder-а сборка запроса превращается в дикую кашу из конкатенаций, где без пол литры и дебаггера не разобраться. - AlexLeonov24.04.2017 12:27- То, что вы написали — это очень простой запрос. 
 Говоря «сложный» я имею в виду, например, вложенные подзапросы, рекурсию или использование оконных функций. Пока что ни один QueryBuilder с таким не справляется — и не нужно!
 
 Имхо, задача QueryBuilder-а это абстрагировать простые запросы от БД.
 Пишем, например, $query->select('*')->from(table)->limit($n)->offset($m) — получаем для каждого драйвера конкретной базы корректно сформированный запрос с учетом диалекта (экранирование и прочее).
 
 Других хороших применений не вижу.
 
 P.S. Ваш пример идеально ложится сюда же. - akubintsev24.04.2017 12:50- Кстати, на тему абстрагирования от конкретной БД. 
 Теоретически конечно прикольно, но нужно ли всегда на практике?
 Одно дело пилим продукт с возможностью инсталлирования третьими лицами, другое дело — свой сервис. А часто ли приходится менять в проекте БД?
 По-моему в большинстве случаев использование этого слоя абстракции не оправдано. - wispoz24.04.2017 13:23- Это имеет место если: Вы делаете сервис для себя например на MSSQL, и потом бац решили перейти на MySQL или Postgresql, да бывает такое, вот тут как раз универсальность и спасет (видел один такой проект). 
 - AlexLeonov24.04.2017 19:12- Нужно. 
 Пример: система миграций в фреймворке. Она должна быть абстрагирована от БД.
 Или метод findById() в какой-нибудь ORM библиотеке. Его нельзя не сделать абстрагированным.
 
 Да и переход с одной БД на другую посередине жизненного цикла проекта мне доводилось проводить, и не раз.
 - VolCh25.04.2017 10:21+1- Даже если не пришлось менять, то порог вхождения в проект нового разработчика понижается. Вот у нас в продакшене несколько проектов на MySQL, PostgreSQL и MS SQL на одной ORM, но их хорошее знание будет лишь плюсом, необходимо знание одной Doctrine. 
 
 - michael_vostrikov24.04.2017 13:05- https://habrahabr.ru/post/327160/#comment_10188340 
 - Говоря «сложный» я имею в виду, например, вложенные подзапросы, рекурсию или использование оконных функций. - Во многих случаях это решается кодом. Либо кодом с временными таблицами. 
 - Других хороших применений не вижу. - Добавление частей запроса по условиям. Когда в зависимости от входных параметров надо сделать или не сделать join с таблицей и where по полю в этой таблице и возможно еще добавить поле в select. И передача этого результата в другую функцию, которая например добавит пагинацию. С конкатенацией в строку будет много дублирующихся проверок и разных поисков в этой строке. Еще полезен не сам QueryBuilder, а его сочетание с ORM, например, когда поля для join автоматически определяются. 
 - VolCh25.04.2017 10:18- С вложенными подзапросами типа WHERE id IN (SELECT ...) вполне справляются рекурсивно. 
 - Как вы предлагаете писать SQL запросы с поддержкой хотя бы синтаксической проверки SQL в IDE, если вид запроса зависит от каких-то параметров, например значений полей для фильтрации, выбранных пользователем? 
 
  - smple24.04.2017 17:11- а чем лучше будет с queryBuilder-oм? тоже самое придется написать 
 - Единственный плюс с queryBuilder-ом порядок условий не важен, но это тоже реализуемо, без нового заморского синтаксиса. 
 - Проблема всех query билдеров в том что надо учить новый синтаксис, вопрос зачем? я вначале представляю какой SQL запрос надо сделать, потом пытаюсь этот SQL преобразовать с помощью доки к библиотеке, зачем мне делать последний шаг и тратить время 
 - Простейшая обертка вроде: 
 - $user = $db->selectOneSQL('SELECT * FROM users WHERE id = :id', ['id' => 123]); var_dump($user); // структура в виде массива $users = $db->selectSQL('SELECT * FROM users'); // возвращаем statment с итератором foreach ($users as $user) { var_dump($user); } $user = $db->selectOne('users', ['id' => 123]); // тот же самый результат что и первый запрос selectOneSQL $db->select('users'); // Тот же самый результат что у второго запроса selectSQL $db->query('UPDATE users SET email = :newValue WHERE id = :id', [ 'newValue' => 'test@mail.ru' ], [ 'id' => 123 ] ); // простейшая обертка над update делает тоже самое $db->update('users', ['email' => 'test@mail.ru'], ['id' => 123]);
 - Чем такой подход хуже ? 
 - только я могу копировать запросы и отлаживать их при желание или делать explain или другие вещи ? 
 - При этом я знаю о ORM и даже иногда использую их только чем ваша библиотека лучше doctrine/dbal ? - AlexLeonov24.04.2017 19:14- Ничем не хуже. Приличная ORM библиотека должна позволять вам делать что-то вроде: 
 
 $users = User::findAllByQuery(new Query('SELECT * FROM users WHERE ...'));
 и вернуть типизированную коллекцию объектов класса User
 
 И действительно в большинстве случаев этого более, чем достаточно.
 
 
 - mayorovp24.04.2017 12:58- Посмотрите как работает Linq в C# — там сложные запросы именно что пишутся на linq на порядок проще чем на голом SQL. Но это языковая фича, для PHP такого сделать в рамках библиотеки нельзя. 
  - sayber24.04.2017 13:10- Писать запрос на SQL, это еще тот маразм, особенно в крупных проектах с большой командой. 
 Программисты общаются на языке проекта, объектами оперировать куда как приятнее и понятнее.
 Все необходимое для понимания, читаем в маппинге и валидации. В будущем объекты тоже сыграют положительную роль.
 
 И не обязательно использовать QB, есть др. методологии.
 
 Встретился я тут год назад с проектом, где код на 800мб и SQL запросы зашиты в нем.
 Найти и проанализировать нужные запросы, оч. тяжело и иногда не реально.
 
- mayorovp24.04.2017 13:07- Чтобы не создавалось проблем при отладке, надо вставлять весь код одной строчкой, а потом добивать пустыми строками до прежнего количества строк. 
 - zzzmmtt24.04.2017 16:48- Я конечно сварщик ненастоящий, но даже вот этот ActiveRecord, хоть мёртвый уже несколько лет как, лучше чем ваше поделие… 
 - funnybanana24.04.2017 22:43-3- Автора уже дважды за такие посты закидывали минусами… 
 При этом ему по второму кругу задают одни и те же вопросы…
 Мне же понятно что ему просто интересно собрать свой (хоть и сомнительный) велосипед…
 На самом деле я тоже люблю велосипеды, но не готов их публиковать на хабре.
 Как пример приведу один php файл который использую для sql запросов (тут не весь функционал а лишь тот который мне нужен для определенного проекта):
 200 строк кода на php (не для слабонервных)- <?php class SQLCONFIG { private $config = [ ["host" => "localhost", "username" => "root", "password" => "24022017", "database" => "bd1", "charset" => "utf8"], ["host" => "localhost", "username" => "root", "password" => "24022017", "database" => "bd2", "charset" => "utf8"] ]; function getConfig($def = 0){ if (isset($this->config[$def])) return (object)$this->config[$def]; } } class SQL extends SQLCONFIG{ private $columns, $rows, $table, $output, $operation, $where, $group, $order, $limit, $join = '', $query; public $sql; function __construct($def = 0){ $config = $this->getConfig($def); if (empty($config)) { $this->error("Отсутствуют настройки для подключения."); }else{ $this->sql = mysqli_connect($config->host, $config->username, $config->password) OR $this->error(); mysqli_select_db($this->sql, $config->database) OR $this->error(); mysqli_set_charset ($this->sql, $config->charset) OR $this->error(); } } function __destruct(){ @mysqli_close($this->sql); } public function close(){ $this->clear(); mysqli_close($this->sql); return; } public function select($table, $columns = "*", $output = "arr"){ $this->operation = __FUNCTION__; $this->table = $this->columns = ""; $this->output = $output; $this->table = "`{$table}`"; if (!is_array($columns)) $columns = explode(",", $columns); foreach ($columns as $key => $value) $this->columns .= "{$this->table}.`".str_replace(".", "`.`", $this->check($value))."`,"; $this->columns = rtrim($this->columns, ","); return $this; } public function where($where = ""){ $this->where = (!empty($where)) ? " WHERE {$where}": ""; return $this; } public function group($group = ""){ $this->group = ""; if (!is_array($group)) $group = explode(",", $group); foreach ($group as $key => $value) $this->group .= "`".str_replace(".", "`.`", $this->check($value))."`,"; $this->group = rtrim($this->group, ","); $this->group = (!empty($group)) ? " GROUP BY {$this->group}" : ""; return $this; } public function order($order = "", $sort = "DESC"){ $this->order = ""; $sort = strtoupper($sort); $sort = ($sort == "DESC" || $sort == "ASC" || $sort == "RAND()") ? $sort : ""; if (!is_array($order)) $order = explode(",", $order); foreach ($order as $key => $value) $this->order .= "`".str_replace(".", "`.`", $this->check($value))."`,"; $this->order = rtrim($this->order, ","); $this->order = (!empty($order)) ? " ORDER BY {$this->order} {$sort}" : ""; return $this; } public function limit($start = 0, $count = 0){ $this->limit = ($count > 0) ? " LIMIT {$start},{$count}" : "LIMIT {$start}"; return $this; } public function join($table, $columns, $on, $prefix = ""){ if (!is_array($columns)) $columns = explode(",", $columns); foreach ($columns as $key => $value){ $dop = (!empty($prefix)) ? " as {$prefix}_{$value}" : ""; $this->columns .= ",`".$this->check($table)."`.`".str_replace(".", "`.`", $this->check($value))."`{$dop}"; } $this->join .= " INNER JOIN `".$this->check($table)."` ON {$on}"; return $this; } public function insert($table, $data = array()){ $this->operation = __FUNCTION__; $this->columns = $this->rows = ""; $this->table = "`{$table}`"; if (isset($data[0])) { foreach ($data[0] as $key => $value) $this->columns .= "`".$this->check($key)."`,"; foreach ($data as $key => $value) { $this->rows .= "("; foreach ($value as $values) $this->rows .= "'".$this->check($values)."',"; $this->rows = rtrim($this->rows, ",")."),"; } }else{ foreach ($data as $key => $value) { $this->columns .= "`".$this->check($key)."`,"; $this->rows .= "'".$this->check($value)."',"; } $this->rows = "(".rtrim($this->rows, ",")."),"; } $this->columns = rtrim($this->columns, ","); $this->rows = rtrim($this->rows, ","); return $this; } public function update($table, $data = array()){ $this->operation = __FUNCTION__; $this->rows = ""; $this->table = "`{$table}`"; foreach ($data as $key => $value) $this->rows .= "`".$this->check($key)."` = '".$this->check($value)."',"; $this->rows = rtrim($this->rows, ","); return $this; } public function delete($table){ $this->operation = __FUNCTION__; $this->table = $this->table = "`{$table}`"; return $this; } public function count($table, $columns="*"){ $this->precounter($table, $columns, __FUNCTION__); return $this; } public function sum($table, $columns){ $this->precounter($table, $columns, __FUNCTION__); return $this; } public function min($table, $columns){ $this->precounter($table, $columns, __FUNCTION__); return $this; } public function max($table, $columns){ $this->precounter($table, $columns, __FUNCTION__); return $this; } public function avg($table, $columns){ $this->precounter($table, $columns, __FUNCTION__); return $this; } private function precounter($table, $columns, $operation = "count"){ $this->operation = $operation; $this->table = "`{$table}`"; $this->columns = ($columns != "*") ? strtoupper($operation)."(`".$this->check($columns)."`) as ".strtolower($operation) : strtoupper($operation)."(*) as ".strtolower($operation); return; } public function query(){ if ($this->operation == "select") { if ($this->output == "only") $this->limit = " LIMIT 0,1"; $query = "SELECT {$this->columns} FROM {$this->table}{$this->join}{$this->where}{$this->group}{$this->order}{$this->limit};"; } if ($this->operation == "insert") { $query = "INSERT INTO {$this->table} ({$this->columns}) VALUES {$this->rows};"; } if ($this->operation == "update") { $query = "UPDATE {$this->table} SET {$this->rows}{$this->where}{$this->group}{$this->order}{$this->limit};"; } if ($this->operation == "delete") { $query = "DELETE FROM {$this->table}{$this->where}{$this->group}{$this->order}{$this->limit};"; } if ($this->operation == "count" || $this->operation == "sum" || $this->operation == "min" || $this->operation == "max" || $this->operation == "avg") { $this->rows = (!empty($this->group)) ? ",".str_replace(" GROUP BY ", "", $this->group) : ""; $query = "SELECT {$this->columns}{$this->rows} FROM {$this->table}{$this->join}{$this->where}{$this->group}{$this->order}{$this->limit};"; } return $query; } public function send($query = ""){ $this->query = (empty($query)) ? $this->query() : $query; $res = mysqli_query($this->sql, $this->query) OR $this->error(); if ($this->operation == "select" || $this->operation == "count" || $this->operation == "sum" || $this->operation == "min" || $this->operation == "max" || $this->operation == "avg") { $result = array(); if ($this->output == "arr") while ($row = mysqli_fetch_assoc($res)) $result[] = $row; elseif ($this->output == "full") while ($row = mysqli_fetch_array($res)) $result[] = $row; elseif ($this->output == "only") $result = mysqli_fetch_assoc($res); elseif ($this->output == "object") while ($row = mysqli_fetch_object($res)) $result[] = $row; else{ if ($res->field_count == 1 && $res->num_rows == 1) { $result = mysqli_fetch_assoc($res); if (isset($result[$this->operation])) $result = (float)$result[$this->operation]; } } }else if($this->operation == "insert"){ $result = (int)mysqli_insert_id($this->sql); }else if($this->operation == "update"){ $result = $res OR $this->error(); }else if($this->operation == "delete"){ $result = $res OR $this->error(); }else{ $result = true; } $this->clear(); return $result; } public function operation($operation){ $this->operation = $operation; return $this; } public function output($output){ $this->output = $output; return $this; } private function error($error = ""){ $error = (empty($error)) ? mysqli_error($this->sql) : $error; die("<div style='background-color:#e95656;color:#fff;font-size:16px;padding:20px;margin:20px;font-family:Consolas,monaco,monospace;'>{$error}</div>"); } private function check($value){ $value = mysqli_real_escape_string($this->sql, $value); return $value; } function clear(){ $this->where = $this->group = $this->order = $this->limit = $this->join = ""; $this->columns = $this->rows = $this->table = $this->output = $this->operation = $this->query = ""; } } ?>
 
 И пару примеров:
 
 - <?php include "sql.php"; $sql = new SQL(0); // 0 - первый конфиг подключения. print_r($sql->select("temp")->send()); // Вернет все строки из таблицы temp print_r($sql->select("temp", "id,name")->where("`id` > 0")->order("date")->limit(0,100)->send()); // а так же всякие там $sql->insert("temp", ["name" => "test"])->send(); // $sql->update("temp", ["name" => "test1"])->send(); // ещё join можно использовать... есть count, SUM, MIN, MAX, AVG... ?>
 - AlexLeonov24.04.2017 23:10-1- Что вас заставляет писать код, не соответствующий PSR-2 (и даже PSR-0, судя по всему)? 
 
 Как вообще можно достичь такого состояния сознания, что вам пофигу на стандарты?
 
 А- else if вообще рвотный рефлекс вызывает, если честно… Ну как же себя можно не уважать, чтобы писать так? - pewpew25.04.2017 00:17-1- Не всем PSR-2 по душе. Не равняйте по себе. 
 Я, например предпочитаю писать так:
 - if (1!=1) { ... } else if (1=1) { ... }
 Удобно бывает копипастить по строчкам, не задумываясь о съехавших блоках.- AlexLeonov25.04.2017 11:06-1- Не всем PSR-2 по душе 
 Если вам не по душе общепринятые стандарты — что вы делаете в этой профессии? - pewpew25.04.2017 13:38+2- Надеюсь, вам не нужно напоминать, что PSR — это рекомендации. И появились они позже языка. 
 И я искренне надеюсь, что вы не из тех, кто лишь по предпочтениям в форматировании кода измеряет его качество.
 Стандарты кодирования безусловно нужны, и в рабочих группах желательно их соблюдение. Но они вовсе не обязаны соответствовать документу PSR-2, если на то пошло.- AlexLeonov25.04.2017 13:43- Я из тех. Вы зря надеялись. 
 
 Несоблюдение общепринятых стандартов — это шаг к Битриксу, если позволите. Это неуважение к сообществу, которое положило многие человеко-годы на разработку этих стандартов, на то, чтобы PHP стал хотя бы немного похож на современный язык программирования, а не на набор скриптов для домашних страничек.
 
 И для меня это, разумеется, неприемлемо. - pewpew25.04.2017 14:00- Вы предлагаете переписать весь код, написанный до 2012 года (принятия PSR-2) и никому его не показывать? А если публиковать, то обязательно приводить к стандарту? 
 Кроме того указанное мной выше предпочтение не простая прихоть и имеет основание. И не важно, что по этому поводу пишут в стандартах. Мне так удобно, и эстетически кажется красивее (это моё субъективное мнение, которое я не навязываю). И такой код удобно поддерживать.
 Даже популярные IDE имеют гибкие настройки по изменению предпочтений в форматировании кода. Казалось бы, зачем это, если придумали PSR-2? Вероятно не всем и не во всех ситуациях годятся эти рекомендации.
 Некоторые до сих пор предпочитают Vim, Notepad++ или например FAR в качестве редактора кода. Что не может быть поводом для пинка из профессии.- AlexLeonov25.04.2017 14:21- А если публиковать, то обязательно приводить к стандарту? 
 Именно так.
 Публикуете код — соблюдайте общепринятые стандарты.
 
 - это моё субъективное мнение 
 С чего вы взяли, что вы имеете право на субъективное мнение в данном вопросе? - pewpew25.04.2017 14:28-1- С чего вы взяли, что вы имеете право на субъективное мнение в данном вопросе? Пожалуй вы правы, вы умнее и лучше меня и остальных.- AlexLeonov25.04.2017 14:32-1- Я — нет, ни в коем случае. Сообщество в целом — да. В PHP-FIG состоят люди, которых я готов признать гораздо умнее и себя и вас вместе взятых. 
 
 Искренне желаю и вам и себе однажды тоже дорасти до такого же уровня.- mayorovp25.04.2017 14:41+2- Да, там и правда люди, которые гораздо умнее вас. 
 - Например, они не стали принудительно вводить проверку соблюдения этого стайл-гайда интерпретатором, оставив возможность программисту самостоятельно принимать решение о том, будет ли он этих правил придерживаться. 
 
 
 
 
 
 
 - mayorovp25.04.2017 14:33- Вы так пишите как будто в общепринятых стандартах не может быть написана фигня - AlexLeonov25.04.2017 14:34- Если считаете, что там написана фигня — вносите свои предложения, проходите экспертизу и голосование. В чём проблема-то? 
 
 
 
  - funnybanana26.04.2017 02:48- Ну вот, судя по комментариям мне просто необходимо изредка вываливать свой «неформат» ибо про PSR-2 не слышал, пойду почитаю. А на счёт else if — так короче, строк меньше, больше строк вмещается на экран — мне удобнее читать (только мне, ибо в команде не работаю, кодом обычно не делюсь) 
 
 И не надо ругаться что не слышал про это соглашение, вроде мониторю хабр регулярно, про php читаю всё…
 
  - cvlab24.04.2017 23:33- За новые идеи могу похвалить, но минусы в первую очередь за то сначала опробовать всё что есть и удостовериться что идея стоит того. 
 
 Из явных недостатков подхода отмечу то, что нельзя сделать динамически изменяемый запрос. - shasoft25.04.2017 09:09- Можно. Забыл в справку добавить. Функция _if(<условие>,...,...) поддерживается в SELECT, OrderBy, GroupBy, Where. В WHERE если без ELSE, то условие заменяется на 1=1 чтобы не ломать логику AND/OR блоков.  - ghost40429.04.2017 01:01- Я бы сказал что проблема в другом. Конструктор запросов означает что запрос может быть составлен не линейно и части запроса применятся в отдельных условиях или даже функциях и файлах. 
 - Простейший пример это вывод списка сущностей с погинацией. Запрос по сути один, но первый будет с - SELECT COUNT(*), а второй с- LIMITи- OFFSET. В вашем случае похоже нужно писать запрос дважды.
 - Еще рекомендую почитать про спецификации из DDD или посмотреть пример реализации для Doctrine.  - shasoft29.04.2017 13:01-1- Такие варианты также предусмотрены. Текст запроса содержится в $__query->sql. Один из вариантов функций возврата результата будет возвращать объект $__query, с которым можно делать что угодно. Можно будет определить количество строк, потом модифицировать запрос и определить текущие строки. 
 
 p.s.А вот про функции LIMIT и OFFSET в select я забыл. Спасибо что напомнили :) - ghost40430.04.2017 20:54- А модификация запроса будет выполнятся через интерфейс QueryBuilder или как строки запроса? 
 В первом случае теряется смысл от кешированя, во втором теряется смысл от самой либы. - shasoft30.04.2017 22:06-2- Скорее всего для пагинации будет сделан отдельный метод, который уже будет внутри себя делать что нужно в рамках библиотеки и отдавать требуемый результат. 
 
 
 
 
 
 
 
           
 
FessAectan
Как-то сложновато все, вы не находите?
Получается мало понимать как написать запрос в консоли любимой СУБД, нужно еще и понять как его интерпретировать используя ваш подход.
Нет проблем, если запрос простой.
Но если он с кучей джоинов, подзапросов и т.д., то это уже будет куда сложнее.
Для MySQL тут проще взять safemysql и передать все нужное через плейсхолдеры.
Подождем более опытных коллег, возможно я не прав.
Zhuravljov
Очень часто использование QueryBuilder-a уместно и оправдано. Вопрос в том, насколько хорошо продумано его API, чтобы он покрывал максимальное количество возможных кейсов.
Наиболее популярный случай, когда запрос дополняется джоинами и условиями в зависимости от внешних факторов. И, например для пагинатора, на основе одной сборки нужно построить два запроса: текущая страница, и общее кол-во страниц. Тут QueryBuilder серьезно упрощает код.
Отличный QueryBuilder у Yii2.
http://www.yiiframework.com/doc-2.0/guide-db-query-builder.html
Единственный минус — неотделим от самого фреймворка. Но в 2.1 планируют вынести в отдельный, не связанный с ядром Yii2, компонент.
SamDark
Не планируем, а рассматриваем возможность. Пока не ясно, будет это в 2.1 или позже.