Как стать автором
Обновить

Комментарии 28

я никогда не пойму зачем люди пишут такое

Для того, чтобы абстрагироваться от конкретной БД, и QueryBuilder сам соберет запрос.

$write_connection->q(
    [
        'DELETE FROM `items` WHERE `id` = ?'
        'DELETE FROM `items_tags` WHERE `item` = ?'
    ],
    $item_id
);

На мой взгляд, не очень хорошо делать такой байндинг, ибо плейсхолдера два, а в байндинг — одна переменная. Слегка путает.
Более того, нет смысла экономить на строчках кода, заворачивая пачку запросов в 1 метод. Единственный плюс от этого — уменьшается количество инструкций function-call, однако еще неизвестно, что под капотом этих Ваших методов.

Дело в том, что абстрагирования там всего ничего, большинство запросов на 100% повторяют синтаксис SQL запросов. Как по мне, так проще сделать несколько простеньких конвертаций как в примерах, и дальше писать SQL.


На самом деле бывает больше двух запросов, тогда и больше строчек моэно сэкономить, и читать визуально проще. Пример 3х запросов.


Под капотом всё очень прямолинейно, но потенциально есть возможность объединять запросы и делать из нескольких запросов один запрос в БД, просто с этим есть некоторые сложности (mysqli_multi_query() ведет себя не совсем так, как хотелось бы, PostgreSQL не желает делать множественные запросы с серверными подготовленными выражениями, SQLite, по-моему, вообще множественные запросы не поддерживает).

По вашим публикациям, пожалуй, можно собрать самый яркий список применения анти-паттернов.
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 (стороннему разработчику придется тратить время и нервы чтобы приноровиться к такому стилю).
Как минимум, 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'));

Вы правда утверждаете, что на это нужно много нервов?

А вот вам сниппет на 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» у вас будет просто замечательный автокомплит.

Я просто выставлю оба примера рядом чтобы было наглядно видно что короче, а что нет. Хотя я бы сказал, что оно соразмерно практически 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 слегка уровнем абстракции выше. Его вы, в конце концов, и здесь можете подключить при желании.

А если у вас будет запрос, который необходимо собирать по-разному в зависимости от нескольких условий? Привет страшненькая конкатенация.

Всё верно. Фреймворк предоставляет низкоуровневые интерфейсы, которые, тем не менее, достаточно функциональны из коробки (согласитесь, не так часто запрос собирается динамически), и при этом является высокопроизводительным.
На базе этих примитивов можно делать что угодно, к примеру, ниже в комментариях интересный пример с Aura.SqlQuery.


Задача в том, чтобы найти здоровый баланс между самым низким уровнем и огромным количеством абстракций, когда уже не понятно что на самом деле происходит под капотом (к примеру, были у меня несколько раз проблемы с Doctrine во время обновления ownCloud — она неправильно определяла версию СУБД и составляла более изощренные запросы, которые в конкретной версии СУБД ещё не поддерживались, приходилось миграции вручную запускать в консоли; понять что идет не так и где было весьма проблематично).

Призываю не минусовать статью, почитать комментарии к подобным вещам бывает занятно. Если конечно, есть что тут комментировать.
В каждой публикации про самопальные 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
    ]
);

Я бы отформатировал иначе, но в целом достаточно приятный вариант, согласен.


$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());
Не вижу никаких проблем кроме того, что именованные подготовленные выражения не поддерживаются.

Вместо
$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?
Как мне выдернуть Ваше творение в свой проект не на CleverStyle Framework?

Во-первых как вы уже упомянули есть куча автономных пакетов, а у CleverStyle Framework цель гибридное, но оптимальное ядро. Соответственно, хотя части и можно разнести по пакетах, есть сомнения, что в этом есть практический смысл.


Практически вся упомянутая в статье логика содержится в четырех классах в core/engines/DB (один общий абстрактный класс и по одному на каждую СУБД). Из специфичного для фреймворка там только использование константы DEBUG. Всё остальное никак не привязано к фреймворку.
Отдельно стоит класс cs\DB, который уже привязан к фреймворку. Он занимается определением того, какую конфигурацию использовать для подключения к БД и управляет соединениями (там же простая балансировка).

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

Чтоб быстро и безболезненно установить нужную часть и использовать если она норм
Пример из текста
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-запрос можно так же динамически составлять, но это однозначно получится гораздо более громоздко, и читаемости кода ничуть не добавит.

Согласен, в таком контексте есть преимущество, спасибо за пример

> ->where($condition)

А как сконструировать условие типа
(a = 'a') AND (b = 'b' OR c = 'c')
или
(a = 'a') OR (b = 'b' AND c = 'c')
?
О, норм, спасибо.

Жаль, что это в документации меньше видно, нежели унылые:
andWhere()
и
orWhere()

Раньше считал QB унылым, но вот в Yii видимо не такой унылый :)

В смысле, чем больше хардкорного SQL, тем лучше?

Почему Вы сделали такой вывод? :)

(Если там хардкорный SQL, то какой же это QB? :^) )
Жаль, что это в документации меньше видно, нежели унылые: andWhere() и orWhere()

Может я вас неправильно понял, но почему унылые?

Они не позволяют сделать (a = 'a') AND (b = 'b' OR c = 'c') и более сложные. (Хардкод не учитываем)

Ну и составлять условия динамически.

Эээ. Laravel:


$query->where('a', 'a')->where(function($query) {
     $query->where('b', 'b')->orWhere('c', 'c');
});
А динамически как это делать? :)

Способ с массивами в where в Yii (по ссылке выше) самый нормальный.
>Query builder так же слишком далёк

+1. Во фреймворках они какие-то унылые.

>Итак, с подходом стало ясно — будем писать SQL.

Это перебор :)

Под капотом всё равно SQL. Я вот теперь тоже если что буду как @ MetaDone кидать линк на Aura.SqlQuery — очень добротно выглядит, и никаких проблем использовать с CleverStyle Framework (как и с любым другим).

> Под капотом всё равно SQL.

Ну это понятно :)

> Aura.SqlQuery — очень добротно выглядит, и никаких проблем использовать с CleverStyle Framework (как и с любым другим).

Я бы не сказал, что ее Query builder чем-то отличается от упомянутых Вами в статье. :)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации