На счёт БД на первый взгляд может показаться, что функциональность из коробки весьма скудная. Отчасти это правда, но компенсируется тем, арсенал очень хорошо продуман, решает поставленные задачи и ориентирован на производительность.
А если вам нужны более функциональные инструменты — их всегда можно до установить по вкусу, это гораздо проще чем выпилить сложного медлительного монстра.
Данная статья покажет основные интерфейсы, а трейты cs\CRUD
и cs\CRUD_helpers
останутся на другой раз.
Без ORM и Query builder-а
ORM не вписывается в идеологию фреймворка (пакет doctrine/orm
, к примеру, без каких-либо зависимостей, даже без учета doctrine/*
пакетов в полтора раза больше всего фреймворка).
Query builder так же слишком далёк, к примеру, я никогда не пойму зачем люди пишут такое (Laravel 5.2):
DB::table('users')->where('name', 'John')->first()
Вместо такого:
SELECT *
FROM `users`
WHERE `name` = 'John'
LIMIT 1
Либо вот ещё (Yii2):
new \yii\db\Query())
->select(['id', 'email'])
->from('user')
->where(['last_name' => 'Smith'])
->limit(10)
Опять таки вместо:
SELECT `id`, `email`
FROM `user`
WHERE `last_name` = 'Smith'
LIMIT 10
Читабельность (ИМХО) хуже, нет подсветки синтаксиса, проверки синтаксиса, статического анализа и автодополнения команд и полей (при сконфигурированной IDE), а при усложнении запросов всё равно проще будет написать чистый SQL чем разбираться в тонкостях работы Query builder-а.
Итак, с подходом стало ясно — будем писать SQL.
SQL бывает разный
Фреймворк на момент написания статьи (версия 5.32.x) поддерживает 3 движка баз данных: MySQL, SQLite, PostgreSQL.
Проблема здесь в том, что синтаксис, поддерживаемый этими СУБД не пересекается на 100% даже в некоторых достаточно часто используемых вещах.
Фреймворк здесь помогает тем, что прозрачно конвертирует часть диалекта MySQL таким образом, чтобы он подходил для SQLite и PostgreSQL.
Далее примеры по большей части дублируют документацию.
SQLite
Здесь только одна совсем небольшая несовместимость:
-- до
INSERT IGNORE INTO `table_name`
(
`text`
) VALUES (
?
)
-- после
INSERT OR IGNORE INTO `table_name`
(
`text`
) VALUES (
?
)
PostgreSQL
Здесь всё сложнее, но тем не менее всё равно достаточно простые преобразования.
Во-первых это кавычки:
-- до
SELECT `id` FROM `table_name`
-- после
SELECT "id" FROM "table_name"
Дальше опять INSERT IGNORE INTO
, для PostgreSQL превращается в INSERT INTO ... ON CONFLICT DO NOTHING
(и поэтому фреймворк требует для работы PostgreSQL 9.5+):
-- до
INSERT IGNORE INTO "table_name"
(
"text"
) VALUES (
?
)
-- после
INSERT INTO "table_name"
(
"text"
) VALUES (
?
)
ON CONFLICT DO NOTHING
Ещё одна похожая команда REPLACE INTO
, она переписывается в существенно более длинную INSERT INTO ... ON CONFLICT ON CONSTRAINT "{table_name}_primary" DO UPDATE SET ...
:
-- до
REPLACE INTO "table_name"
(
"id",
"item",
"value"
) VALUES (
?,
?,
?
)
-- после
INSERT INTO "table_name"
(
"id",
"item",
"value"
) VALUES (
?,
?,
?
)
ON CONFLICT ON CONSTRAINT "table_name_primary" DO UPDATE SET
"id" = EXCLUDED."id",
"item" = EXCLUDED."item",
"value" = EXCLUDED."value"
Важно заметить, что в этом случае фреймворк ожидает что для таблицы есть constraint
(не знаю как лучше перевести) с именем таблицы и суффиксом _primary
, например, для системной таблицы [prefix]users
он выглядит следующим образом:
ALTER TABLE ONLY "[prefix]users" ADD CONSTRAINT "[prefix]users_primary" PRIMARY KEY ("id");
Последний нюанс связан с тем, в каком формате PostgreSQL желает получать серверные подготовленные выражения, так что всегда можно использовать ?
:
-- до
SELECT "id" FROM "table_name" WHERE `number` > ? AND `age` < ? LIMIT ?
-- после
SELECT "id" FROM "table_name" WHERE `number` > $1 AND `age` < $2 LIMIT $3
Немного об БД в общем
Фреймворк изначально имеет понятие о том, что БД может быть несколько. Каждая БД может использовать разные движки, или одинаковые движки, но с разными конфигурациями. Так же поддерживаются зеркала БД и простое распределение запросов в конфигурациях master-master и master-slave.
Модули, которые используют БД, указывают в своём meta.json
2 ключа, которые относятся к БД (пример из системного модуля):
{
...
"db" : [
"keys",
"texts",
"users"
],
"db_support" : [
"MySQLi",
"PostgreSQL",
"SQLite"
],
...}
В db_support
указывается, с какими движками модуль в принципе может работать, в db
указываются названия баз данных, которые будут во время установки ассоциированы с какой-либо из существующих БД.
Разные названия используются для того, чтобы иметь возможность выбрать наиболее оптимальную БД под задачу. Само собой, таблицы должны быть распределены таким образом, чтобы не делать JOIN
между разными БД.
Позже, когда нужно получить id базы данных, ассоциированной с названием можно таким образом:
$db_id = \cs\Config::instance()->module('System')->db('users');
Далее идентификатор используется для получение объекта с подключением к нужной БД:
$write_connection = \cs\DB::instance()->db_prime($db_id);
$read_connection = \cs\DB::instance()->db($db_id);
Уже здесь разработчик явно указывает, будет ли он что-то писать в БД, или нет. От этого зависит выбор зеркала при соответственной конфигурации.
DBAL
Здесь всё просто, как только вы уловите принцип — вы сможете очень продуктивно писать запросы с закрытыми глазами.
Простое выполнения запроса
Простое выполнение запроса:
$result = $read_connection->q('SELECT `id` FROM `table_name`');
q
это сокращение от query
. У метода есть несколько вариантов синтаксиса:
->q($query_string : string)
->q($query_string : string, ...$parameters : array)
->q($query_string : string, $parameters : array)
->q($query_string : string[])
->q($query_string : string[], ...$parameters : array)
->q($query_string : string[], $parameters : array)
Сами запросы могут использовать как серверные подготовленные выражения:
$write_connection->q(
[
'DELETE FROM `items` WHERE `id` = ?'
'DELETE FROM `items_tags` WHERE `item` = ?'
],
$item_id
);
Так и клиентское форматирование в виде синтаксиса функции sprintf()
:
$write_connection->q(
[
'DELETE FROM `items` WHERE `id` = %d'
"DELETE FROM `items_tags` WHERE `item` = '%s'"
],
$item_id
);
В последнем примере перед подстановкой данные будут обработаны соответствующим образом, так что в '%s'
SQL-инъекции не будет.
Для серверных подготовленных выражений позволяется использовать не все аргументы (в отличии от прямого использования нативных интерфейсов):
$write_connection->q(
[
"DELETE FROM FROM `[prefix]articles` WHERE `id` = ?",
"DELETE FROM FROM `[prefix]articles_comments` WHERE `article` = ? OR `date` < ?",
"DELETE FROM FROM `[prefix]articles_tags` WHERE `article` = ?"
],
[
$article_to_delete,
time() - 24 * 3600
]
);
Выборка данных
Второй полезный метод предназначен для непосредственного получения данных:
$read_connection->f($result);
f
это сокращение от fetch
. У метода так же есть несколько необязательных параметров:
->f($query_result, $single_column = false : bool, $array = false : bool, $indexed = false : bool)
$single_column === true
вместо массива с колонками вернет скалярное значение первой колонки:
$read_connection->f(
$read_connection->q('SELECT `id` FROM `table_name` WHERE `id` = 1')
); // ['id' => 1]
$read_connection->f(
$read_connection->q('SELECT `id` FROM `table_name` WHERE `id` = 1'),
true
); // 1
$array === true
вместо одной строки считает все и вернет результат в виде массива:
$read_connection->f(
$read_connection->q('SELECT `id` FROM `table_name` WHERE `id` < 3'),
false,
true
); // [['id' => 1], ['id' => 2]]
$read_connection->f(
$read_connection->q('SELECT `id` FROM `table_name` WHERE `id` = 1'),
true,
true
); // [1, 2]
$indexed === true
возвращает индексированный массив вместо ассоциативного:
$read_connection->f(
$read_connection->q('SELECT `id` FROM `table_name` WHERE `id` < 3'),
false,
false,
true
); // [1]
$read_connection->f(
$read_connection->q('SELECT `id` FROM `table_name` WHERE `id` = 1'),
false,
true,
true
); // [[1], [2]]
А теперь интересные сокращения:
->qf() === ->f(->q(...))
->qfa() === ->f(->q(...), false, true)
->qfs() === ->f(->q(...), true)
->qfas() === ->f(->q(...), true, true)
a
от array
, а s
от single
.
К примеру, следующие две конструкции эквивалентны, хотя вторую читать и сопровождать сильно проще:
$read_connection->f(
$read_connenction->q('SELECT `id` FROM `table_name` WHERE `id` = ?', 1),
true,
true
); // [1, 2]
$read_connection->qfas(
'SELECT `id` FROM `table_name` WHERE `id` = ?',
1
); // [1, 2]
Вставка данных
Ещё есть один иногда полезный метод для вставки данных:
$write_connection->insert(
'INSERT INTO `table_name`
(`id`, `value`)
VALUES
(?, ?)',
[
[1, 12],
[2, 13],
[3, 14]
]
);
Синтаксис следующий:
->insert($query : string, $parameters : array|array[], $join = true : bool)
Если $join === true
, то пример выше будет перед выполнением переписан как:
$write_connection->q(
'INSERT INTO `table_name`
(`id`, `value`)
VALUES
(?, ?),
(?, ?),
(?, ?)',
[
1, 12,
2, 13,
3, 14
]
);
Иначе строки будут вставляться по одной.
Прочие методы
Есть ещё ряд полезных методов, к примеру, ->id()
вернет идентификатор последней вставленной строки, ->transaction()
позволяет обернуть выполнение операций в транзакцию:
$write_connection->transaction(function ($c) { // `$c` это то же, что и `$write_connection`
$c->insert(...);
// Вложенные транзации на самом деле пустышки, всё выполняется в рамках родительской транзакции
$c->transaction(function ($c) {
$c->id();
});
// Если бросить исключение или вернуть `false` то будет выполнен откат транзакции, исключение будет проброшено дальше
});
Есть методы для получения списка таблиц и колонок в таблице, которые работают одинаково для всех поддерживаемых БД и некоторые другие вспомогательные вещи.
В целом, за подробностями обращайтесь к документации.
На этом введение в базовую работу с БД всё
В многих модулях вместо прямых запросов используются удобные трейты cs\CRUD
и cs\CRUD_helpers
.
Первый кроме непосредственно 4 банальных операций с БД под капотом ещё умеет заниматься многоязычностью, нормализацией и некоторой обработкой данных (например, JSON туда и обратно конвертировать при записи и чтении), обработкой загружаемых файлов, а так же поддерживает связанные таблицы (один к одному, один ко многим), тоже с поддержкой всего упомянутого добра.
Второй же трейт имеет метод для поиска (на самом деле это фильтр) элементов, опять таки учитывая многоязычность некоторых полей/таблиц и так же включает поддержку связанных таблиц.
Если добавить описание обоих трейтов в статью, то она будет слишком большая на один раз, поэтому будет в следующий раз.
Мысли по поводу удобства интерфейсов и примеры более удобных (по вашему мнению) альтернатив приветствуются, буду рад обсудить данные моменты в конструктивном ключе и учесть обратную связь в будущих релизах.
Комментарии (28)
zenn
22.08.2016 11:41+4По вашим публикациям, пожалуй, можно собрать самый яркий список применения анти-паттернов.
Query builder так же слишком далёк, к примеру, я никогда не пойму зачем люди пишут такое (Laravel 5.2):
И жаль что не поймете. Как минимум, QueryBuilder в паре с ActiveRecord (laravel5, yii2) позволяют куда удобней работать с БД чем писать запросы вручную. Кроме того, ООП-синтаксис запросов (builder/ar) куда приятней plain-text'a в sql, а при адекватно настроенной IDE позволяет существенно упростить их написание (при помощи autocomplete). Посмотрите так же на длину и лаконичность запроса через builder/ar либо классический sql. Выше вам так же намекнули на гибкость билдера для разных типов серверов СубДБ.
Ваша реализация с передачей запросов в ->q() [query], ->f() [fetch] имеет сомнительную полезность по сравнению с классическом pdo_prepared, а следующий код поднимает волосына задницена голове дыбом:
->qf() === ->f(->q(...))
->qfa() === ->f(->q(...), false, true)
->qfs() === ->f(->q(...), true)
->qfas() === ->f(->q(...), true, true)
В querybuilder'e вы не увидете таких извращений, а запросы select->fetch далаются в 1 строку и вполне читаемы. Такой конструкцией вы усложняете разработку под вашу cms другим людям, ведь это вовсе не классический вид pdo_prepared запросов и не querybuilder (стороннему разработчику придется тратить время и нервы чтобы приноровиться к такому стилю).nazarpc
22.08.2016 11:58-2Как минимум, QueryBuilder в паре с ActiveRecord (laravel5, yii2) позволяют куда удобней работать с БД чем писать запросы вручную.
Для этого есть
cs\CRUD
иcs\CRUD_helpers
, которые позволяют вообще обойтись и без SQL и без конструирования запросов. Всё сводится к декларативному описанию структуры таблиц и вызовом одного метода.
Пример в системном классе: структура, чтение, обновление данных.
Кроме того, ООП-синтаксис запросов (builder/ar) куда приятней plain-text'a в sql, а при адекватно настроенной IDE позволяет существенно упростить их написание (при помощи autocomplete). Посмотрите так же на длину и лаконичность запроса через builder/ar либо классический sql.
В том то и дело, что в примерах Query builder не короче, а из автодополнения вы получите только методы. В то время как в SQL вы получите красивую подсветку синтаксиса, автодополнение SQL команд, в том числе тех, о которых Query builder не знает, а так же полей из реальных таблиц. Query builder ну никак здесь не выигрывает у SQL, даже близко. Это не учитывая того, что SQL может читать любой, а в синтаксисе конкретного Query builder-а и его ограничениях нужно ещё разбираться.
В querybuilder'e вы не увидете таких извращений, а запросы select->fetch далаются в 1 строку и вполне читаемы.
А здесь, простите, сколько строчек? Чем вам не читаем любезно раскрашенный IDE SQL?
Такой конструкцией вы усложняете разработку под вашу cms другим людям, ведь это вовсе не классический вид pdo_prepared запросов и не querybuilder (стороннему разработчику придется тратить время и нервы чтобы приноровиться к такому стилю).
Хорошо, давайте сравним. Для примера беру сниппет из документации:
$sth = $dbh->prepare('SELECT name, colour, calories FROM fruit WHERE calories < ? AND colour = ?'); $sth->execute(array(150, 'red')); $red = $sth->fetchAll(); $sth->execute(array(175, 'yellow')); $yellow = $sth->fetchAll();
А теперь CleverStyle Framework (полная аналогия):
$query = 'SELECT name, colour, calories FROM fruit WHERE calories < ? AND colour = ?'; $red = $dbh->qfa($query, array(150, 'red')); $yellow = $dbh->qfa($query, array(175, 'yellow'));
Вы правда утверждаете, что на это нужно много нервов?
zenn
22.08.2016 12:43+1А вот вам сниппет на laravel active record(или замените класс Fruit на DB::table('fruit') если модель не описана):
$red = Fruit::select('name', 'colour', 'calories')->where('calories', '<', 150)->where('colour', 'red')->get(); $yellow = Fruit::select('name', 'colour', 'calories')->where('calories', '<', 175)->where('colour', 'yellow')->get();
как по мне — куда удобней и приятней, чем оба варианта выше.
В том то и дело, что в примерах Query builder не короче, а из автодополнения вы получите только методы
Не короче? Ну посчитайте, хоть через strlen, однозначно короче. Если вы пойдете дальше и будете использовать ActiveRecord с описанием моделей, в том числе phpdoc «property type $column» у вас будет просто замечательный автокомплит.nazarpc
22.08.2016 13:01-2Я просто выставлю оба примера рядом чтобы было наглядно видно что короче, а что нет. Хотя я бы сказал, что оно соразмерно практически 1 в 1, поскольку, повторюсь, в подобных ситуациях query builder прямо дублирует SQL синтаксис.
$red = Fruit::select('name', 'colour', 'calories')->where('calories', '<', 150)->where('colour', 'red')->get(); $red = $dbh->qfa('SELECT name, colour, calories FROM fruit WHERE calories < ? AND colour = ?', 150, 'red');
Если вы пойдете дальше и будете использовать ActiveRecord с описанием моделей, в том числе phpdoc «property type $column» у вас будет просто замечательный автокомплит.
Согласен, но мы же говорим пока (имеется ввиду содержимое поста) о произвольных запросах. ActiveRecord слегка уровнем абстракции выше. Его вы, в конце концов, и здесь можете подключить при желании.
franzose
23.08.2016 07:44А если у вас будет запрос, который необходимо собирать по-разному в зависимости от нескольких условий? Привет страшненькая конкатенация.
nazarpc
23.08.2016 08:28Всё верно. Фреймворк предоставляет низкоуровневые интерфейсы, которые, тем не менее, достаточно функциональны из коробки (согласитесь, не так часто запрос собирается динамически), и при этом является высокопроизводительным.
На базе этих примитивов можно делать что угодно, к примеру, ниже в комментариях интересный пример с Aura.SqlQuery.
Задача в том, чтобы найти здоровый баланс между самым низким уровнем и огромным количеством абстракций, когда уже не понятно что на самом деле происходит под капотом (к примеру, были у меня несколько раз проблемы с Doctrine во время обновления ownCloud — она неправильно определяла версию СУБД и составляла более изощренные запросы, которые в конкретной версии СУБД ещё не поддерживались, приходилось миграции вручную запускать в консоли; понять что идет не так и где было весьма проблематично).
Zazza
22.08.2016 12:39Призываю не минусовать статью, почитать комментарии к подобным вещам бывает занятно. Если конечно, есть что тут комментировать.
MetaDone
22.08.2016 13:31В каждой публикации про самопальные Query builder'ы скидываю эту ссылку. Компонент от ауры я могу одной строкой подключить в свой проект, Ваш — не знаю как долго и имеет ли смысл выдирать его
Взять хотя бы insert
<?php $insert = $query_factory->newInsert(); $insert->into('foo') // insert into this table ->cols(array( // insert these columns and bind these values 'foo' => 'foo_value', 'bar' => 'bar_value', 'baz' => 'baz_value', )); ?>
и Ваш вариант
$write_connection->q( 'INSERT INTO `table_name` (`id`, `value`) VALUES (?, ?), (?, ?), (?, ?)', [ 1, 12, 2, 13, 3, 14 ] );
nazarpc
22.08.2016 13:49Я бы отформатировал иначе, но в целом достаточно приятный вариант, согласен.
$write_connection->q( 'INSERT INTO `table_name` (`foo`, `bar`, `baz`) VALUES (?, ?, ?)', ['foo_value', 'bar_value', 'baz_value'] );
Компонент от ауры я могу одной строкой подключить в свой проект, Ваш — не знаю как долго и имеет ли смысл выдирать его
Здесь в зависимость модуля одна строчка в
meta.json
(пример), и ещё одна если ещё нет зависимости от Composer (пример).
The query objects do not execute queries against a database. When you are done building the query, you will need to pass it to a database connection of your choice.
Не вижу никаких проблем кроме того, что именованные подготовленные выражения не поддерживаются.
Пример из документации:
// a PDO connection $pdo = new PDO(...); // prepare the statment $sth = $pdo->prepare($select->getStatement()); // bind the values and execute $sth->execute($select->getBindValues()); // get the results back as an associative array $result = $sth->fetch(PDO::FETCH_ASSOC);
А здесь получится следующее:
$db = \cs\DB::instance()->module('System')->db('users'); $result = $db->qf($select->getStatement(), $select->getBindValues());
MetaDone
22.08.2016 14:09Не вижу никаких проблем кроме того, что именованные подготовленные выражения не поддерживаются.
Вместо
$write_connection->q( [ "DELETE FROM FROM `[prefix]articles` WHERE `id` = ?", "DELETE FROM FROM `[prefix]articles_comments` WHERE `article` = ? OR `date` < ?", "DELETE FROM FROM `[prefix]articles_tags` WHERE `article` = ?" ], [ $article_to_delete, time() - 24 * 3600 ] );
было бы
$write_connection->q( [ "DELETE FROM FROM `[prefix]articles` WHERE `id` = :article_id", "DELETE FROM FROM `[prefix]articles_comments` WHERE `article` = :article_id OR `date` < :clear_time", "DELETE FROM FROM `[prefix]articles_tags` WHERE `article` = :article_id" ], [ "article_id"=>$article_to_delete, "clear_time"=>time() - 24 * 3600 //коряво, но так все равно лучше и понятнее ] );
The query objects do not execute queries against a database. When you are done building the query, you will need to pass it to a database connection of your choice.
Это к тому, что тут только собираются запросы, для их исполнения можно использовать https://github.com/auraphp/Aura.Sql/
Вы привели пример получения результата запроса через PDO, можно сделать это же через Aura.Sql:
$pdo = new ExtendedPdo(...); $result = $pdo->fetchAll($select->getStatement(), $select->getBindValues()); //или же foreach ($pdo->yieldAll($select->getStatement(), $select->getBindValues()) as $row) { // ... } //и т.п.
Здесь в зависимость модуля одна строчка в meta.json (пример), и ещё одна если ещё нет зависимости от Composer (пример).
Как мне выдернуть Ваше творение в свой проект не на CleverStyle Framework?nazarpc
22.08.2016 14:18Как мне выдернуть Ваше творение в свой проект не на CleverStyle Framework?
Во-первых как вы уже упомянули есть куча автономных пакетов, а у CleverStyle Framework цель гибридное, но оптимальное ядро. Соответственно, хотя части и можно разнести по пакетах, есть сомнения, что в этом есть практический смысл.
Практически вся упомянутая в статье логика содержится в четырех классах в core/engines/DB (один общий абстрактный класс и по одному на каждую СУБД). Из специфичного для фреймворка там только использование константы
DEBUG
. Всё остальное никак не привязано к фреймворку.
Отдельно стоит класс cs\DB, который уже привязан к фреймворку. Он занимается определением того, какую конфигурацию использовать для подключения к БД и управляет соединениями (там же простая балансировка).MetaDone
22.08.2016 14:32хотя части и можно разнести по пакетах, есть сомнения, что в этом есть практический смысл.
Чтоб быстро и безболезненно установить нужную часть и использовать если она норм
vshemarov
22.08.2016 13:39+3Пример из текста
new \yii\db\Query()) ->select(['id', 'email']) ->from('user') ->where(['last_name' => 'Smith']) ->limit(10);
Один из плюсов, о котором вообще не упоминается в статье, это возможность конструировать запросы налету:
new \yii\db\Query()) ->select($fields) ->from($table) ->where($condition) ->limit($limit);
И эти $fields, $table, $condition, $limit — это все выражения, которые могут определяться, в зависимости от контекста и множества условий. Плюс сама конструкция запроса может так же налету меняться:
if (!empty($search)) { $query->andWhere($condition2); } if (!empty($needOrder)) { $query->orderBy([ 'id' => SORT_ASC, 'name' => SORT_DESC, ]); }
Я понимаю, что при большом желании и прямой SQL-запрос можно так же динамически составлять, но это однозначно получится гораздо более громоздко, и читаемости кода ничуть не добавит.G-M-A-X
25.08.2016 10:18> ->where($condition)
А как сконструировать условие типа
(a = 'a') AND (b = 'b' OR c = 'c')
или
(a = 'a') OR (b = 'b' AND c = 'c')
?Zhuravljov
25.08.2016 13:01G-M-A-X
25.08.2016 18:02-1О, норм, спасибо.
Жаль, что это в документации меньше видно, нежели унылые:
andWhere()
и
orWhere()
Раньше считал QB унылым, но вот в Yii видимо не такой унылый :)franzose
26.08.2016 04:59В смысле, чем больше хардкорного SQL, тем лучше?
G-M-A-X
26.08.2016 08:58+1Почему Вы сделали такой вывод? :)
(Если там хардкорный SQL, то какой же это QB? :^) )franzose
26.08.2016 09:02Жаль, что это в документации меньше видно, нежели унылые: andWhere() и orWhere()
Может я вас неправильно понял, но почему унылые?
G-M-A-X
26.08.2016 10:35Они не позволяют сделать (a = 'a') AND (b = 'b' OR c = 'c') и более сложные. (Хардкод не учитываем)
Ну и составлять условия динамически.
G-M-A-X
25.08.2016 10:09-1>Query builder так же слишком далёк
+1. Во фреймворках они какие-то унылые.
>Итак, с подходом стало ясно — будем писать SQL.
Это перебор :)
nazarpc
25.08.2016 12:53Под капотом всё равно SQL. Я вот теперь тоже если что буду как @ MetaDone кидать линк на Aura.SqlQuery — очень добротно выглядит, и никаких проблем использовать с CleverStyle Framework (как и с любым другим).
G-M-A-X
25.08.2016 17:54-1> Под капотом всё равно SQL.
Ну это понятно :)
> Aura.SqlQuery — очень добротно выглядит, и никаких проблем использовать с CleverStyle Framework (как и с любым другим).
Я бы не сказал, что ее Query builder чем-то отличается от упомянутых Вами в статье. :)
Miraage
Для того, чтобы абстрагироваться от конкретной БД, и QueryBuilder сам соберет запрос.
На мой взгляд, не очень хорошо делать такой байндинг, ибо плейсхолдера два, а в байндинг — одна переменная. Слегка путает.
Более того, нет смысла экономить на строчках кода, заворачивая пачку запросов в 1 метод. Единственный плюс от этого — уменьшается количество инструкций function-call, однако еще неизвестно, что под капотом этих Ваших методов.
nazarpc
Дело в том, что абстрагирования там всего ничего, большинство запросов на 100% повторяют синтаксис SQL запросов. Как по мне, так проще сделать несколько простеньких конвертаций как в примерах, и дальше писать SQL.
На самом деле бывает больше двух запросов, тогда и больше строчек моэно сэкономить, и читать визуально проще. Пример 3х запросов.
Под капотом всё очень прямолинейно, но потенциально есть возможность объединять запросы и делать из нескольких запросов один запрос в БД, просто с этим есть некоторые сложности (
mysqli_multi_query()
ведет себя не совсем так, как хотелось бы, PostgreSQL не желает делать множественные запросы с серверными подготовленными выражениями, SQLite, по-моему, вообще множественные запросы не поддерживает).