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)
Akuma
24.04.2017 09:29+4Как по мне, так велосипед с первой статьи не изменился. Единственный «плюс», который я вижу: переменные биндятся сразу на месте, а не в конце списком параметров. Даже если опустить удобство биндинга в конце, ваш вариант проигрывает тупо из-за неудобства написания SQL в вашем синтаксисе.
Если уж так хочется достичь того же самого, сделайте проще:
ML(«SELECT id, name FROM table WHERE name = {$name}»);
Парсите такие строки и так же автоматически подменяйте переменные на безопасный биндинг параметров. Таким образом вы убьете всех зайцев разом: стандартный синтаксис, нормальная подсветка синтаксиса, IDE ни на что не ругается и вы получаете свой профит в виде биндига на месте.shasoft
24.04.2017 10:08-3Самый первый вариант таким и был. В этом случае гораздо сложнее парсинг выражения. И каждый JOIN усложняет этот парсинг на порядок.
keksmen
24.04.2017 10:32+1На самом деле, нет. При правильном построении грамматики запроса последовательный разбор становится достаточно несложной задачей.
shasoft
24.04.2017 21:22К примеру такой простой запрос:
ML(«SELECT id, name FROM table WHERE {$name}={$fieldname}»);
каким образом понять где у нас имя поля, а где имя значения? Т.е. уже нужны какие-то модификаторы для обозначения этого. Добавить к этому куски запроса, которые вставляются по запросу — уже получается не так просто.
В теории — да, действительно просто. Поэтому и делал так. Как говорится, дьявол, он в деталях. И в данном случае этих деталей много. В результате требуется полноценный разборщик строки на лексемы и гораздо проще использовать парсер PHP.YemSalat
25.04.2017 11:48-1каким образом понять где у нас имя поля, а где имя значения?
Да, придется разбирать SQL выражение. Но по-моему это был бы гораздо более интересный и полезный проект, т.к. то что вы предлагаете, как уже замечено — уже 1000 раз реализовано.
Akuma
24.04.2017 11:04+1Ну а как вы хотели?
Сейчас вы пошли легким для себя, но бессмысленным для окружающих путем. Для «домашнего» использования это может и прокатит, а вот для массового — увы, нет.
AterCattus
24.04.2017 10:36+6Оба поста выглядят как
«Я вот придумал штуку, которую уже много раз делали, но я вам свой вариант все-равно не покажу.»
basili4
24.04.2017 10:46-1Мой же код предварительно нужно компилировать и на выполнение запускать уже откомпилированную версию
Я вот чего не пойму как будет предварительно компилироваться запрос select * from pages p where p.page_id=(номер страницы переданный в запросе к странице);?
И какой смысл в предворительной компиляции запроса в строку, если сам запрос и его план можно кешировать на стороне сервера?
QuickJoey
24.04.2017 11:08И эти люди запрещают использовать хранимые процедуры в качестве application server. Глядя на картинки становится невыносимо печально, а ведь это простейший запрос. Чуть сложнее логика, не дай бог с временными таблицами в запросе и всё, в этой каше разобраться будет нереально. Попробуйте хранимые процедуры, вам понравится.
michael_vostrikov
24.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();
Вместо небольших временных таблиц в языке есть массивы, да и работа с большими не сильно отличается от обычных.
QuickJoey
24.04.2017 12:31Я немного про другое. Вместо универсального построителя любых запросов, использовать процедуры под конкретную задачу. Процедура называется, например, userprofile_get, и на вход ждёт два параметра _sex и _net. Тогда вызов будет что-то типа:
call userprofile_get (_sex='M', _net='VK;OK');
а внутри этой процедуры может быть какой угодно сложный запрос, который оставит код на PHP/Ruby/c++ лёгким.
michael_vostrikov
24.04.2017 12:48+2Проблема в том, что его надо поддерживать. А поддерживать код на обычном языке программирования гораздо проще.
Zhuravljov
24.04.2017 12:28+2Кто запрещает то? Если хранимая процедура дает серьезный выигрыш по совокупности факторов производительности и сопровождаемости, то почему бы и нет.
Просто их сложнее поддерживать в актуальном состоянии. Я про условия командной разработки, где актуальность кода гарантирует VCS, а структуры БД — миграции. И тут получается, что хранимая процедура — не рыба не мясо, не код и не структура. Изменения хранимок в миграциях держать неудобно, потому что это код, и неотъемлемая часть общей логики, и было бы неплохо иметь возможность с помощью Git-а посмотреть историю изменений в этом самом коде. Но и в sql-файлах их держать тоже не так удобно, потому что нет гарантий, что хранимка скомпилится, если миграциями не обновили структуру до совместимого с ней состояния.
Но, если в команде выработаны общие подходы по работе с хранимками, то нет проблем.
AlexLeonov
24.04.2017 11:09-1Все писатели подобных QueryBuilder-ов упорно не понимают простую истину: чем сложнее запрос, тем проще написать его на SQL, без всяких QueryBuilder-ов.
Делайте проще — и в этом будет смысл. Постараетесь учесть все нюансы SQL — и вы изобретете заново SQL, но с дурными синтаксисом и кучей ограничений, запутаетесь и сделаете бессмысленный комбайн, который всё равно работает от силы в половине случаев.Zhuravljov
24.04.2017 11:23+2Спорное утверждение. Все сильно зависит от ситуации. Если ваш запрос сложный но монолитный, то да, проще на чистом SQL. А если наличие каких-то джоинов и условий зависит от внешних факторов, то без QueryBuilder-а сборка запроса превращается в дикую кашу из конкатенаций, где без пол литры и дебаггера не разобраться.
AlexLeonov
24.04.2017 12:27То, что вы написали — это очень простой запрос.
Говоря «сложный» я имею в виду, например, вложенные подзапросы, рекурсию или использование оконных функций. Пока что ни один QueryBuilder с таким не справляется — и не нужно!
Имхо, задача QueryBuilder-а это абстрагировать простые запросы от БД.
Пишем, например, $query->select('*')->from(table)->limit($n)->offset($m) — получаем для каждого драйвера конкретной базы корректно сформированный запрос с учетом диалекта (экранирование и прочее).
Других хороших применений не вижу.
P.S. Ваш пример идеально ложится сюда же.akubintsev
24.04.2017 12:50Кстати, на тему абстрагирования от конкретной БД.
Теоретически конечно прикольно, но нужно ли всегда на практике?
Одно дело пилим продукт с возможностью инсталлирования третьими лицами, другое дело — свой сервис. А часто ли приходится менять в проекте БД?
По-моему в большинстве случаев использование этого слоя абстракции не оправдано.wispoz
24.04.2017 13:23Это имеет место если: Вы делаете сервис для себя например на MSSQL, и потом бац решили перейти на MySQL или Postgresql, да бывает такое, вот тут как раз универсальность и спасет (видел один такой проект).
AlexLeonov
24.04.2017 19:12Нужно.
Пример: система миграций в фреймворке. Она должна быть абстрагирована от БД.
Или метод findById() в какой-нибудь ORM библиотеке. Его нельзя не сделать абстрагированным.
Да и переход с одной БД на другую посередине жизненного цикла проекта мне доводилось проводить, и не раз.
VolCh
25.04.2017 10:21+1Даже если не пришлось менять, то порог вхождения в проект нового разработчика понижается. Вот у нас в продакшене несколько проектов на MySQL, PostgreSQL и MS SQL на одной ORM, но их хорошее знание будет лишь плюсом, необходимо знание одной Doctrine.
michael_vostrikov
24.04.2017 13:05https://habrahabr.ru/post/327160/#comment_10188340
Говоря «сложный» я имею в виду, например, вложенные подзапросы, рекурсию или использование оконных функций.
Во многих случаях это решается кодом. Либо кодом с временными таблицами.
Других хороших применений не вижу.
Добавление частей запроса по условиям. Когда в зависимости от входных параметров надо сделать или не сделать join с таблицей и where по полю в этой таблице и возможно еще добавить поле в select. И передача этого результата в другую функцию, которая например добавит пагинацию. С конкатенацией в строку будет много дублирующихся проверок и разных поисков в этой строке. Еще полезен не сам QueryBuilder, а его сочетание с ORM, например, когда поля для join автоматически определяются.
VolCh
25.04.2017 10:18С вложенными подзапросами типа WHERE id IN (SELECT ...) вполне справляются рекурсивно.
Как вы предлагаете писать SQL запросы с поддержкой хотя бы синтаксической проверки SQL в IDE, если вид запроса зависит от каких-то параметров, например значений полей для фильтрации, выбранных пользователем?
smple
24.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 ?
AlexLeonov
24.04.2017 19:14Ничем не хуже. Приличная ORM библиотека должна позволять вам делать что-то вроде:
$users = User::findAllByQuery(new Query('SELECT * FROM users WHERE ...'));
и вернуть типизированную коллекцию объектов класса User
И действительно в большинстве случаев этого более, чем достаточно.
mayorovp
24.04.2017 12:58Посмотрите как работает Linq в C# — там сложные запросы именно что пишутся на linq на порядок проще чем на голом SQL. Но это языковая фича, для PHP такого сделать в рамках библиотеки нельзя.
sayber
24.04.2017 13:10Писать запрос на SQL, это еще тот маразм, особенно в крупных проектах с большой командой.
Программисты общаются на языке проекта, объектами оперировать куда как приятнее и понятнее.
Все необходимое для понимания, читаем в маппинге и валидации. В будущем объекты тоже сыграют положительную роль.
И не обязательно использовать QB, есть др. методологии.
Встретился я тут год назад с проектом, где код на 800мб и SQL запросы зашиты в нем.
Найти и проанализировать нужные запросы, оч. тяжело и иногда не реально.
mayorovp
24.04.2017 13:07Чтобы не создавалось проблем при отладке, надо вставлять весь код одной строчкой, а потом добивать пустыми строками до прежнего количества строк.
zzzmmtt
24.04.2017 16:48Я конечно сварщик ненастоящий, но даже вот этот ActiveRecord, хоть мёртвый уже несколько лет как, лучше чем ваше поделие…
funnybanana
24.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... ?>
AlexLeonov
24.04.2017 23:10-1Что вас заставляет писать код, не соответствующий PSR-2 (и даже PSR-0, судя по всему)?
Как вообще можно достичь такого состояния сознания, что вам пофигу на стандарты?
Аelse if
вообще рвотный рефлекс вызывает, если честно… Ну как же себя можно не уважать, чтобы писать так?pewpew
25.04.2017 00:17-1Не всем PSR-2 по душе. Не равняйте по себе.
Я, например предпочитаю писать так:
if (1!=1) { ... } else if (1=1) { ... }
Удобно бывает копипастить по строчкам, не задумываясь о съехавших блоках.AlexLeonov
25.04.2017 11:06-1Не всем PSR-2 по душе
Если вам не по душе общепринятые стандарты — что вы делаете в этой профессии?pewpew
25.04.2017 13:38+2Надеюсь, вам не нужно напоминать, что PSR — это рекомендации. И появились они позже языка.
И я искренне надеюсь, что вы не из тех, кто лишь по предпочтениям в форматировании кода измеряет его качество.
Стандарты кодирования безусловно нужны, и в рабочих группах желательно их соблюдение. Но они вовсе не обязаны соответствовать документу PSR-2, если на то пошло.AlexLeonov
25.04.2017 13:43Я из тех. Вы зря надеялись.
Несоблюдение общепринятых стандартов — это шаг к Битриксу, если позволите. Это неуважение к сообществу, которое положило многие человеко-годы на разработку этих стандартов, на то, чтобы PHP стал хотя бы немного похож на современный язык программирования, а не на набор скриптов для домашних страничек.
И для меня это, разумеется, неприемлемо.pewpew
25.04.2017 14:00Вы предлагаете переписать весь код, написанный до 2012 года (принятия PSR-2) и никому его не показывать? А если публиковать, то обязательно приводить к стандарту?
Кроме того указанное мной выше предпочтение не простая прихоть и имеет основание. И не важно, что по этому поводу пишут в стандартах. Мне так удобно, и эстетически кажется красивее (это моё субъективное мнение, которое я не навязываю). И такой код удобно поддерживать.
Даже популярные IDE имеют гибкие настройки по изменению предпочтений в форматировании кода. Казалось бы, зачем это, если придумали PSR-2? Вероятно не всем и не во всех ситуациях годятся эти рекомендации.
Некоторые до сих пор предпочитают Vim, Notepad++ или например FAR в качестве редактора кода. Что не может быть поводом для пинка из профессии.AlexLeonov
25.04.2017 14:21А если публиковать, то обязательно приводить к стандарту?
Именно так.
Публикуете код — соблюдайте общепринятые стандарты.
это моё субъективное мнение
С чего вы взяли, что вы имеете право на субъективное мнение в данном вопросе?pewpew
25.04.2017 14:28-1С чего вы взяли, что вы имеете право на субъективное мнение в данном вопросе?
Пожалуй вы правы, вы умнее и лучше меня и остальных.AlexLeonov
25.04.2017 14:32-1Я — нет, ни в коем случае. Сообщество в целом — да. В PHP-FIG состоят люди, которых я готов признать гораздо умнее и себя и вас вместе взятых.
Искренне желаю и вам и себе однажды тоже дорасти до такого же уровня.mayorovp
25.04.2017 14:41+2Да, там и правда люди, которые гораздо умнее вас.
Например, они не стали принудительно вводить проверку соблюдения этого стайл-гайда интерпретатором, оставив возможность программисту самостоятельно принимать решение о том, будет ли он этих правил придерживаться.
mayorovp
25.04.2017 14:33Вы так пишите как будто в общепринятых стандартах не может быть написана фигня
AlexLeonov
25.04.2017 14:34Если считаете, что там написана фигня — вносите свои предложения, проходите экспертизу и голосование. В чём проблема-то?
funnybanana
26.04.2017 02:48Ну вот, судя по комментариям мне просто необходимо изредка вываливать свой «неформат» ибо про PSR-2 не слышал, пойду почитаю. А на счёт else if — так короче, строк меньше, больше строк вмещается на экран — мне удобнее читать (только мне, ибо в команде не работаю, кодом обычно не делюсь)
И не надо ругаться что не слышал про это соглашение, вроде мониторю хабр регулярно, про php читаю всё…
cvlab
24.04.2017 23:33За новые идеи могу похвалить, но минусы в первую очередь за то сначала опробовать всё что есть и удостовериться что идея стоит того.
Из явных недостатков подхода отмечу то, что нельзя сделать динамически изменяемый запрос.shasoft
25.04.2017 09:09Можно. Забыл в справку добавить. Функция _if(<условие>,...,...) поддерживается в SELECT, OrderBy, GroupBy, Where. В WHERE если без ELSE, то условие заменяется на 1=1 чтобы не ломать логику AND/OR блоков.
ghost404
29.04.2017 01:01Я бы сказал что проблема в другом. Конструктор запросов означает что запрос может быть составлен не линейно и части запроса применятся в отдельных условиях или даже функциях и файлах.
Простейший пример это вывод списка сущностей с погинацией. Запрос по сути один, но первый будет с
SELECT COUNT(*)
, а второй сLIMIT
иOFFSET
. В вашем случае похоже нужно писать запрос дважды.
Еще рекомендую почитать про спецификации из DDD или посмотреть пример реализации для Doctrine.
shasoft
29.04.2017 13:01-1Такие варианты также предусмотрены. Текст запроса содержится в $__query->sql. Один из вариантов функций возврата результата будет возвращать объект $__query, с которым можно делать что угодно. Можно будет определить количество строк, потом модифицировать запрос и определить текущие строки.
p.s.А вот про функции LIMIT и OFFSET в select я забыл. Спасибо что напомнили :)ghost404
30.04.2017 20:54А модификация запроса будет выполнятся через интерфейс QueryBuilder или как строки запроса?
В первом случае теряется смысл от кешированя, во втором теряется смысл от самой либы.shasoft
30.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 или позже.