Как стать автором
Поиск
Написать публикацию
Обновить

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

перед запросом на добавление первоначально выполняется запрос на чтение SELECT * FROM ... LIMIT 0,1

Удивился, полез посмотреть, и действительно, на самом деле выполняется более логичный запрос с LIMIT 0.

в итоге выполнится ПОДГОТОВЛЕННЫЙ запрос

Всё-таки, слово "подготовленный" имеет строго определённое значение. Здесь лучше подойдёт "отформатированный".

Самые важные методы связаны непосредственно с безопасностью и экранированием:

...и видимо поэтому показаны максимально невнятно, и без исходника в них разобраться довольно сложно. Спасибо, кстати, некоему А.В.Шаталову за то что код можно нормально посмотреть. Самому Битриксу это, судя по всему, не нужно.

Экранированный SQL

Судя по всему, автор статьи сам не понимает, что делает метод convertToDb, и почему его всегда следует предпочесть методу forSql. А функция на самом деле интересная. Хотя есть и спорные моменты. Вот эта привычка молча обрезать строку по длине наверняка подпортила нервов не одному поколению программистов: они думали что данные записались норм - ошибок ведь не было - а потом через полгода выплывает, что там какие-то обрезки.

Также в хелпере есть ряд методов для формирования запросов с префиксом prepare.

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

Транзакции

И никто не догадался сделать простую онанимку, чтобы писать

$db->transaction(function () use ($db) {
    $db->queryExecute('UPDATE my_table SET active = "N" WHERE age > 0');
    \Bitrix\Main\SiteTable::update('s1', [
        'ACTIVE' => 'N',
    ]);  
});

а не каждый раз всю эту колбасу на пол-экрана.

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

Удивился, полез посмотреть, и действительно, на самом деле выполняется более логичный запрос с LIMIT 0.

Действительно, опечатка в статье. Спасибо!

Всё-таки, слово "подготовленный" имеет строго определённое значение. Здесь лучше подойдёт "отформатированный".

Верное замечание, убрал по тексту использования термина, чтобы не путать умы юнных читателей :)

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

Раскрыл использование методов convert* детальнее ;-)

Спасибо, кстати, некоему А.В.Шаталову за то что код можно нормально посмотреть. Самому Битриксу это, судя по всему, не нужно.

Зачем, если исходники у нас всегда под рукой? :) Если серьезно, то проблема с докой и справочником API известная, мы ей занимаемся!

Судя по всему, автор статьи сам не понимает, что делает метод convertToDb, и почему его всегда следует предпочесть методу forSql

Завидую вашей способности читать чужие мысли, но в данном случае это лишь вопрос неверной терминологии, а не непонимания)

Не всегда стоит использовать convertToDb , вместо forSql . В целом сам класс SqlHelper , является хорошим примером когда стоит использовать forSql .

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

Вот эта привычка молча обрезать строку по длине наверняка подпортила нервов не одному поколению программистов: они думали что данные записались норм - ошибок ведь не было - а потом через полгода выплывает, что там какие-то обрезки.

Надеюсь мы оба понимаем, что это неправда ;) Обрезка происходит не "молча", а только в случае указания длинны в методе convertToDbString , либо при указании размера в ORM в некоторых классов Bitrix\Main\ORM\Fields\* .

В случае ORM, вместо указания размера и обрезки, можно использовать Bitrix\Main\ORM\Fields\Validators\LengthValidator

И снова термин, который во всём остальном мире означает одно, а в битриксе - что-то совсем другое.

Более подходящего термина к сожалению не нашлось, т.к. в любом случае это именно подготовка SQL запроса.

... Но при этом что именно другое - в статье не объясняется!

Очень жаль, что вы не заметили блок кода который следует сразу за этой фразой :(

И никто не догадался сделать простую онанимку

Пока вы пишите простой код на 2 строки, то действительно выглядит лучше. Если речь уже про более большие куски кода, что замыкание, что try/catch выглядят одинаково ;)

Действительно, опечатка в статье. Спасибо!

Я не написал выше, хотя надо было: эта опечатка значимая. Сразу возникает вопрос, а что будет, если в таблице нет данных? А если все работает даже если запрос не вернет ни одной строки, то зачем тогда её выбирать :)

Завидую вашей способности читать чужие мысли

Ну кстати да, интересно, как это работает. Если проанализировать ход мысли, приведший к такому выводу, то будет примерно так: поскольку отличие этих двух функций никак не объясняется, а сама фраза, "Экранированный SQL", является технически некорректной, можно предположить, что автор действительно вкладывает в convertToDb какой-то особый смысл. Но я рад что это был вопрос терминологии, который сейчас исправлен.

В целом сам класс SqlHelper , является хорошим примером когда стоит использовать forSql .

Не очень понял, что имеется в виду. forSql используется в SqlHelper, фактически, только один раз, для реализации convertToDbString.

В любом случае, нет ни одной причины использовать forSql вместо convertToDb где бы то ни было.

По поводу обрезания я всё-таки придерживаюсь позиции "если длина строки превышает разрешённую, то при валидировании входящих данных выдаём пользовательскую ошибку, а в остальных случаях - системную"

Очень жаль, что вы не заметили блок кода который следует сразу за этой фразой :(

Блок кода рассказывает, как делать upsert. А что означает слово prepare в названиях методов, он вообще никак не объясняет.

Пока вы пишите простой код на 2 строки

Ну так он и должен быть на две строки. Если больше, то надо вынести формирование запросов в отдельные методы. Тогда любой код транзакции будет содержать столько строк, сколько в ней запросов. Плюс "на простыне кода разница уже не будет заметна", и функция не нужна - так себе аргумент. Тут вполне можно было согласиться, что да, такой метод в Битриксе не помешал бы. С другой стороны, вы и так много где согласились с моим, как всегда не слишком куртуазным разбором :)

Не очень понял, что имеется в виду. forSql используется в SqlHelper, фактически, только один раз, для реализации convertToDbString.

В любом случае, нет ни одной причины использовать forSql вместо convertToDb где бы то ни было.

Один раз, зато какой :) Тут я имел ввиду сам класс как кейс использования forSql.

Если говорить про конструирование SQL запросов без ORM, даже банальных LIKE конструкций, то convertToDb не подойдёт, и там нужно использовать только forSql :

$sql = "WHERE name LIKE '%" . $sqlHelper->forSql($value) . "%'";

Ну так он и должен быть на две строки. Если больше, то надо вынести формирование запросов в отдельные методы. Тогда любой код транзакции будет содержать столько строк, сколько в ней запросов. Плюс "на простыне кода разница уже не будет заметна", и функция не нужна - так себе аргумент.

С таким примером соглашусь, что наличие замыкания имеет смысл и будет удобно.

Я не очень люблю когда в качестве аргументации приводят очень простые примеры, которые чаще всего далеки от жизни, поэтому не упустил возможности указать на это :)
Саму реализацию через коллбэк возьмём на заметку и возможно реализуем, спасибо!. Насколько знаю уже во всех фреймворках и ORM такая история есть, не будем отставать от устаревающих трендов :)

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

Если со всем соглашаться, то времени не хватит ни на ответы, ни на реализацию, поэтому приходится достаточно консервативно подходить ко всему новому :D

там нужно использовать только forSql

Вот здесь я реально удивился. Я не могу даже вообразить, почему вы так решили. При том что нет совершенно никаких проблем добавить в запрос строковый литерал целиком, а не собирать его из огрызков. Почему вы считаете, что с использованием convertToDb этот запрос написать нельзя? Подготовленные (в общепринятом смысле) запросы ведь как-то справляются же?

Это был пример когда нужно вшивать какой-то кусок в какой-то SQL.

При желании можно использовать и convertToDb :

$sql = "WHERE name LIKE " . $sqlHelper->convertToDb("%{$value}%");

Это был пример когда нужно вшивать какой-то кусок в какой-то SQL.

Эта фраза мне активно не нравится. Она наводит на некие подозрения, которые я не буду здесь озвучивать, но главное в том, что она в корне неверна.

Вы не можете использовать forSql() чтобы вшивать какой-то кусок в какой-то SQL. Вы можете использовать forSql() только для того же, для чего используется convertToDb(): чтобы "вшивать" в SQL строковый литерал, только за большее количество шагов. Никакой другой кусок вы с её помощью встраивать не должны.

Именно поэтому, при наличии convertToDb(), использование forSql() превращается в бессмыслицу: зачем добавлять кавычки вручную, если есть функция, которая сделает это за нас.

ORM справляется с чтением / записью в колонки с json-native и прочих СУБД-специфичных типов данных, или из коробки только текст, числа и булево?

Конкретно с JSON справляется: можно использовать поле Bitrix\Main\ORM\Fields\ArrayField , при сохранении/чтении будет выполнятся преобразование данных в/из JSON.

Про работу с бинарными данными в статье упоминание есть.

На счет "прочих типов", тут уже нужна конкретика :)

GEOMETRY например)

Такой экзотики из коробки нет :) В данном случае можно добавить свое поле:

Кастомное поле
use Bitrix\Main\ORM\Fields\ScalarField;

abstract class Geometry
{}

class GeometryField extends ScalarField
{
    public function cast($value)
    {
        if ($this->is_nullable && $value === null)
        {
            return null;
        }

        if ($value instanceof Geometry)
        {
            return $value;
        }

        return Geometry::createFromWKT($value);
    }

    public function convertValueFromDb($value)
    {
        return $this->cast($value);
    }

    public function convertValueToDb($value)
    {
        if ($value === null)
        {
            if ($this->is_nullable)
            {
                return null;
            }
            
            throw new Exception('Is not nullable');
        }
        
        if ($value instanceof Geometry)
        {
            return $value->toWKT();
        }
        
        throw new Exception('Invalid value');
    }
}

Спасибо, интересное решение, надо попробовать

Зарегистрируйтесь на Хабре, чтобы оставить комментарий