Взгляните на код PHP:

$user->v_useragent = 'coresky.agent';

Такой код может спровоцировать SQL запрос UPDATE или INSERT, а может и не спровоцировать, если идентичные данные уже установлены в БД, собственно поэтому этот функционал именуется «Призрачные SQL запросы». Похожий функционал, обычно присутствует в большинстве CRM, но давайте рассмотрим, как он может быть реализован без CRM. Призрачные запросы, имеют потенциал довольно широко применяться в веб-приложениях, особенно в части конфигураций. Типичный (но не обязательно) алгоритм проходит в три этапа: чтение данных из БД, изменение данных, возможно многократное, и формирований реальных SQL запросов для обновления данных в БД. Давайте разберемся в деталях…

Всего необходимо иметь три метода, по одному для каждого этапа. Код первого метода, который осуществляет распаковку прочитанных из БД данных приведу полностью, так как он довольно прост:

class SKY // код из проекта coresky
{
...
    static function &ghost($char, $original, $sql = '', $flag = 0) {
        SKY::$mem[$char] = [$flag, $flag & 4 ? null : $original, $sql, []];
        if ($sql)
            trace('GHOST SQL: ' . (is_array($sql) ? end($sql) : $sql), false, 1);
        if ($original) foreach (explode("\n", unl($original)) as $v) {
            list($k, $v) = explode(' ', $v, 2);
            SKY::$mem[$char][3][$k] = unescape($v, true);
        }
        return SKY::$mem[$char][3];
    }

Входные параметры метода:

  • $char — одна маленькая буква английского алфавита, указывает на тип функционала призрачных запросов, может использоваться в префиксах переменных, как в примере выше;
  • $original — текстовое содержимое ячейки БД (`mediumtext` для MySQL), где скопом хранятся все переменные призрачных запросов $char. На вход может подаваться и пустая строка;
  • $sql — шаблон запроса, который будет использоваться для генерации реального SQL запроса, например в callback функции для register_shutdown_function();
  • $flag — флаг. Чаще используется предустановленное значение 0;

Шаблоны запроса могут быть двух типов, на основе функций sql(..) или sqlf(..). Код второй приведу полностью, так как он, при правильном использовании, гарантирует невозможность SQL инъекций, он быстр и довольно прост:

function sqlf() { # quick parsing, using printf syntax. No SQL injection!
    $in = func_get_args();
    $tpl = array_shift($in);
    if ($pos = strpos($tpl, '$'))
        $tpl = preg_replace('/\$_(\w+)/', T_PREFIX . '$1', $tpl);
  
    $sql = vsprintf($tpl, array_map(function ($a) {
        if (!is_array($a))
            return is_num($a) ? $a : escape($a); # escape ALL if not numeric
        return implode(', ', array_map(function ($v) {
            return is_num($v) ? $v : escape($v); # escape ALL if not numeric
        }, $a));
    }, $in));
    return sql(1, $sql);
}

К сожалению, функция sqlf() не универсальна, с точки зрения составления произвольных SQL запросов в аспекте защиты от инъекций. Однако она существует параллельно с универсальной функцией sql(), из-за сравнительно высокой скорости работы. Шаблоны для sqlf() используются для случая работы только лишь с одной ячейкой mediumtext. Второй же шаблон, используется когда нужно организовать такие отложенные запросы для многих колонок таблицы.

Как происходит обработка, указанного кода PHP в самом начале статьи


Переменная $user содержит указатель на объект USER, который, в свою очередь, имеет магические методы __get() и __set(). По префиксу v_ класс понимает, что происходит запись в таблицу сессий `visitors` и вызыват метод SKY::save(..) который сохраняет код в массиве SKY::$mem. По окончанию работы скрипта, вызывается callback функция для register_shutdown_function(), в котором собственно осуществляется (или нет) реальный SQL запрос в БД.

Итак, остальные два метода, которые необходимы для организации функционала, это SKY::save(..) и SKY::here(..). Первый сохраняет данные в массив, а второй генерирует и выполняет реальный запрос в БД.

В коде coresky (повторно используемый код фреймворк или CMF) используется 8 типов призрачных SQL:

  • системная конфигурация, которая хранится в БД;
  • конфигурация посетителей;
  • конфигурация авторизованных пользователей;
  • системная конфигурация админки;
  • системная конфигурация консольных запусков;
  • три типа призрачных SQL используются для организации утилиты i18n;

Как видим «призрачные SQL» актуальны практически для всех web-приложений.

Достоинства описываемого функционала


  • не нужно делать ALTER TABLE… (или INSERT новых рядов) чтобы добавить новые переменные конфигураций, когда приложение развивается. Можно добавлять новые переменные просто в PHP коде, не изменяя структуру БД;
  • функционал может сократить количество запросов к БД до одного, для случая когда происходит запись в один и тот-же ряд одной и той же таблицы;

Недостаток


Для переменных ghost SQL нельзя «прикрутить» индексы, скорее всего, нельзя сделать LOCK TABLE или использовать другие расширенные возможности MySQL.

Больше информации можно прочесть на сайте проекта SKY

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


  1. eandr_67
    08.05.2019 19:21

    Помнится, мольеровский Журден не знал, что всю жизнь говорил прозой. А автор статьи не знает, что пытается изобрести велосипед, много лет назад получивший название Data Mapper.

    P.S. ORM Doctrine2, реализующая в PHP паттерн Data Mapper, выпущена в 2010 году.


    1. coresky Автор
      08.05.2019 19:44

      Раз уж Вы все знаете, может подскажете почему он так слабо применяется? Вещь реально, имхо, крутая. И почему он в Doctrine2 называется Repository?


      1. eandr_67
        08.05.2019 20:18

        Можете привести ссылки на статистические данные, демонстрирующие слабое применение Doctrine (учитывающие, что Doctrine используется далеко не только в Symfony)?

        ORM, построенные на AR — это большая скорость разработки и меньшие требования к квалификации разработчиков. Для малых и (частично) средних проектов разработка на AR обходится дешевле, чем на DM.


        1. coresky Автор
          08.05.2019 20:33
          -1

          Как Вам такая идея (давно ее лелею): автор статьи, является дискжокеем, вы открываете статью и у вас играет музыка: Lana Del Rey — Music To Watch Boys To

          Ну согласитесь автор старался, статью написал, это же честно, что-бы у «зашедших» играла его музыка?


      1. OnYourLips
        09.05.2019 15:35

        Раз уж Вы все знаете, может подскажете почему он так слабо применяется?
        Он крайне широко применяется (дефолтный инструмент во втором по популярности фреймворке), просто есть определенные требования к квалификации сотрудников таких проектов.



    1. coresky Автор
      08.05.2019 20:21
      -1

      Вообще, Data Mapper плохо описан в Wiki, но по примерам я понял, что там делается наследие от некоторого базового класса, потом можно применять. В призрачных SQL — не нужно строить дополнительные классы. Просто пользоваться тремя вышеописанными методами, и итоге получить два функциональных аспекта. Во-первых, накапливать SQL update/replace/update, во-вторых, имеется возможность хранить много переменных в одной ячейке. Да и вообще, я указал вначале статьи, что похожие методики используются в ORM, но не нужно строить или пользоваться ORM чтобы иметь этот функционал под рукой


    1. rjhdby
      09.05.2019 01:27

      Изобретение велосипедов — это очень хороший способ разобраться, как они устроены.


  1. rjhdby
    09.05.2019 01:30

    Вот зачем!? Зачем я в ночь на праздник увидел этот код!? Бедные мои глаза!


  1. kolu4iy
    09.05.2019 11:08

    С точки зрения администратора БД ORM — это 1с, производительность которого можно залить деньгами, но и то не всегда. Все же сильно правильнее, когда запросы к БД формализуемы, таблицы корректно индексированы, а кеш таки используется по назначению…
    Потому поддерживаю про малые и иногда средние проекты.


  1. Tatikoma
    09.05.2019 13:54

    По окончанию работы скрипта, вызывается callback функция для register_shutdown_function(), в котором собственно осуществляется (или нет) реальный SQL запрос в БД.

    А если скрипт не оканчивает работу — как быть?
    А если скрипт оканчивает работу, но shutdown-функция не выполняется?


    1. coresky Автор
      10.05.2019 04:58
      -1

      1. Если скрипт не оканчивает работу, вызовите вручную когда надо SKY::here(), это же просто :). Если вы заметили, то в тексте написано «как правило»…
      2. shutdown-функция всегда выполняется, иного не наблюдал ни разу в php начиная с 5.4.
      Ошибок до регистрации callback хендлера быть не может (чтобы он не сработал), — не то место, чтобы там писать код приложения. Да… shutdown-функция может не выполниться, когда выполняется другая и в ней выполнили exit. — не нужно писать другие shutdown-функции, это тоже просто (это не прерогатива кода приложения). Никогда я не видел чтобы это была проблема.

      Я так понимаю, ваш пост — попытка поймать меня на элементарном незнании PHP, но если вы хоть немного смотрели мою работу, то это глупо, иначе вы сами не знаете элементарный PHP


      1. Tatikoma
        11.05.2019 17:25

        Я ни на чём вас не ловлю, просто задал вопросы. Вы почему-то упустили вариант, когда на сервере просто пропадёт питание — тогда shutdown-функция явно не будет выполнена. Соответственно весь код нужно писать с оглядкой на то, что shutdown-функция выполнена не будет, а если мы каждый раз обрабатываем эту ситуацию — тогда зачем вообще shutdown-функция?


        1. coresky Автор
          11.05.2019 19:47

          Если пропадает питание, то никакой код не будет выполнен, который подразумевалось что должен. Любой критически важный код, например финансовые транзакции, нужно писать «с оглядкой». Если писать конфигурационный код, то часто «оглядка» не нужна, например у меня в системной конфигурации прописана размер авки 100х100 если я прописую 120х120 и в этот момент вырубили свет, то после того как включили, я просто проверил руками, успел ли прописать, если нет,- еще раз повторил. Так что оглядка должна быть всегда, но здесь вы упоминаете не по теме. Я описую идею, основные моменты. А вообще пользуйтесь ИБП :)


          1. Tatikoma
            11.05.2019 20:39

            Вообще я подводил мысль к транзакциям и согласованности данных в разных компонентах системы, в частности к многофазных коммитам, но кажется вам это ни о чем не говорит.

            Нужно рассматривать все возможные ситуации и обрабатывать их, иначе получится то, что называется «неопределённое поведение», которое может быть как безобидным, так и критичным, вне зависимости от того сохраняете вы «размер авки» или «финансовые транзакции».

            Самое главное — вы забываете о переиспользовании кода. Сегодня вы сохраняете «размер авки», а завтра у вас юзеры платят за «размер авки» деньгами которые попадают в «финансовые транзакции». Ваш код могут использовать только юзеры с ИБП, по вашей логике.


  1. ajaxtelamonid
    09.05.2019 14:18
    +1

    Ваша коллаборация с Тарасом КТЛ взорвёт интернеты. Пожалуй, это единственный человек, который может воспринять ваш фреймворк всерьёз, свяжитесь с ним.


  1. Rukis
    09.05.2019 17:43

    1. Не получается зарегистрироваться на сайте вашего фреймворка, чтобы принять участие в разработке, на почту приходит ссылка, но когда по ней перехожу выдает «попытка взлома заблокирована». Пробовал несколько раз.

    2. Не нашёл списка контрибьюторов, сколько живых человек принимает участие в проекте?

    3. Можете привести несколько сайтов реализованных на этом фреймворке?

    4. Есть ли пример исходного кода небольшого типичного проекта реализованного на этом фреймворке? Чтобы можно было быстро оценить как выглядит практика его применения.


    1. coresky Автор
      10.05.2019 06:06
      -1

      1. coresky код постоянно обновляется, к сожалению иногда происходят такие накладки. Попробуйте зарегистрироваться через OAuth2 вашего аккаунта Google, доселе OAuth2 регистрация была устойчива. Тесты тоже пока не написаны, чтобы быстро фиксить такие вещи, если бы было хотябы 5 человек у меня — было бы проще.
      2. Я один и финансирования 0, поэтому хотелось бы найти серьезное финансирование желательно за бугром а не в совках и человек 5-6 хороших программистов, которые хотя бы изредка ранее повторяли мантру «что-то с PHP right way все-таки не так»
      3,4. Cмотрите в разделе загрузок — MED.CRM.SKY. Там есть DEMO и стабильный код сайта без одного файла (локально развернуть рабочую копию не сможете). Это код не самый свежий (в нем нет SKY-GATE (небесных врат), о них можете прочесть статью), но все-же в нем используется одна из последних редакций coresky кода. В данный момент я работаю над SKY-GATE, работа не закончена.

      ru.coresky.net/download


      1. coresky Автор
        10.05.2019 08:09

        Поремонтировал регистрацию через email — должна работать, извините за неудобство.
        Выслал вам недостающий файл на вашу почту яндекс, так что сможете развернуть локально MED.CRM.SKY.


  1. coresky Автор
    11.05.2019 05:48

    Уважаемые читатели хабра, я пишу по-видимому свой последний пост на хабре! Это не значит что я сдался, я просто не буду больше писать на хабре и отвлекать ваше внимание. По итогу за эту мою статью проголосовало 12 в минус и 11 в плюс. Я обращаюсь к тем кто проголосовал в минус. Вот реально без стёба, очень прошу, кто-нибудь может написать, реально, что плохо? Без стёба, пожалуйста!


    1. rjhdby
      11.05.2019 10:27

      В коде для повторного использования, для организации «призрачных запросов» необходимо иметь три метода

      Почему именно три? Что они должны делать?


      , первый приведу полностью, так как он довольно прост:

      Может он и прост, но настолько безобразно написан, что разбираться в нем нет никакого желания.


      где скопом хранятся все переменные призрачных запросов $char.

      Эм… Шта!?


      И т.д. Какой-то бессвязный поток сознания.


      1. coresky Автор
        11.05.2019 11:15

        Спасибо за честный ответ.
        У меня когда-то в контактах был чел у него в статусе было написано: «Я и сам не понимаю своих идей», я сейчас понял для чего. Ну не умею, я делать красивую обертку. Честно говоря, я рассчитывал, что тот кому нужно, прочтет дополнительную одноименную статью на моем сайте, — там подробнее описано.
        Но тем не менее, признаю, я плохо постарался. Обещаю по возможности исправить эту статью.


        1. sumanai
          11.05.2019 18:14

          Немного не туда, ну да ладно.

          Честно говоря, я рассчитывал, что тот кому нужно, прочтет дополнительную одноименную статью на моем сайте, — там подробнее описано.

          Ничего не подробнее. Я например так и не понял, как мне добавить своё поле в этот призрачный запрос. Добавить свою букву в SKY::$mem, как? Реализовать три метода, где и как?
          В качестве примера вам необходимо было бы пройти весь путь добавления своей таблицы с классом.
          Впрочем открыл код вашего фреймворка (как его скачать, я таки не понял):
          <?php
           
          # For Licence and Disclaimer of this code, see http://coresky.net/license
          # Filename: unique
          # Portname: mysqli_ php module (clear cloud)
           
          function trace($var, $show_error = false, $line = 0, $file = '', $context = null) {
              global $sky;

          Закопайте пока никто не видел, мне хватило 8 строчек.


          1. Tatikoma
            11.05.2019 20:32
            +1

            Позволю себе уточнить, что именно не так с кодом:
            1. Для таких комментариев следует использовать phpdoc. И его же следует использовать для описания аргументов функции.
            2. У функции слишком много аргументов. Названия аргументов не дают понять, за что именно эти аргументы отвечают.
            3. Использование глобальных переменных, — это как минимум моветон. Не следует использовать global.
            4. В современном PHP есть очень удобный тайпхинтинг, хорошо бы его здесь использовать.


            1. coresky Автор
              11.05.2019 21:08
              -2

              Боюсь вас рассмешить, но по поводу глобалов читайте мою предыдущую статью. Я настаиваю на том, что PHP right way и большинство PSR — ошибочная рекомендация. Объекты $sky и $user (как минимум) нужно чтобы были пользовательскими (точнее фреймворка) суперглобалами, нет смысла инжектить их повсюду и использовать длинные имена. Тоже самое «phpdoc» хрень несусветная, сорри за французский.

              PSR это клетка

              Иногда нужно всё разрушить, спалить всё дотла, чтобы потом начать всё сначала.
              — Клей (Ирвин Уэлш)
              Эти слова красиво звучат в одной из песен группы Флёр
              www.youtube.com/watch?v=rU6EXBkL2d0&list=RD4uQaxCS84Io&index=6


              1. sumanai
                11.05.2019 21:34

                Боюсь вас рассмешить

                Это не смешно, это грустно.
                Я настаиваю на том, что PHP right way и большинство PSR — ошибочная рекомендация.

                Я настаиваю на том, что вы ошибаетесь. И не я один.
                нет смысла инжектить их повсюду и использовать длинные имена

                Если у вас user инъектится повсюду, вы делаете что-то не так.
                Тоже самое «phpdoc» хрень несусветная, сорри за французский.

                Вот подсказки IDE на ваш код

                А вот с phpDoc комментариями (извиняюсь за комментарии на русском, да и вообще, они скорее всего ошибочны, но суть не в этом)

                Что проще и понятнее?


    1. Tatikoma
      11.05.2019 17:33

      Вы написали свой код только по той причине, что весь остальной открытый код имеет фатальный недостаток (надеюсь всем знаком этот термин). Ваш реальный уровень знаний не соответствует тому уровню, на котором вы себя видите.
      Пока же вы занимаетесь изобретением того, что уже есть. И это, на самом деле, — неплохой подход, — но это не то, что интересно.
      Рискну сделать предположение, что вы довольно молоды и лет через 5-10 ваши статьи будут более благосклонно восприниматься сообществом.


  1. Nathan_Foxy
    11.05.2019 17:53

    Почитал статью и заминусил на всякий, чтобы этого больше никто не видел.