image

Так как ORM слишком тяжеловесны для моих нужд, то обычно я использовал DbSimple. Однако после знакомства с Twig, которые компилирует шаблоны в php код периодически возникала идея написать что-то аналогичное для работы с БД. И вот я это сделал. На картинке представлен запрос на PHP, который после компиляции генерирует код для создания и выполнения SQL запроса.

В первой реализации я компилировал запрос из синтаксиса аналогичного DbSimple в PHP код. Идея была в том, чтобы на выходе получить готовый код с нативными функциями без всяких оберток. При этом можно было наворачивать запросы любой сложности и скорость их разбора не влияла на время работы, так как после компиляции это был обычный нативный код. Однако сложность в отладке таких запросов (сложно было искать ошибки в синтаксисе SQL) и тот факт что время разбора запроса не так велико по сравнению с выполнением запроса привели к тому, что от идеи использовать такой подход я отказался.

Не так давно наткнулся на библиотеку по разбору PHP кода на лексемы PHP-Parser. По работе я пишу код на языке ABAP, в котором команды по работе с БД встроены в сам язык, поэтому возникла идея «А что если сделать что-то подобное для PHP»?

Схема реализации достаточно простая и очевидная: при автозагрузке класса проверяем его наличие в директории компилированных классов. Если нет скомпилированного класса, то берем исходный файл класса, заменяем в нем все спец-команды и записываем готовый класс в директорию скомпилированных классов. Весь разбор делает библиотека PHP-Parser. Чтобы компилятор мог понять, что это именно нужная ему команда, оборачиваем все в пространство имен ML (Macro Language). К примеру в коде пишем так:

\ML::SQL(Select(
    'aa,bb', ucase('xx')->as('Uxx')
),		
from("MyTable")->as("tab"),
where( 
    like('aa','sha%'),
    _or(
        field('bb') == NULL,
        field('bb') == 2006
    )
),
orderBy(    
    "-aa,+bb"
),
into($rows)->rows());

и получаем на выходе:

$__driver0 = \ML\SQL::getDriver('c933f3523437d521bf59e9e6077255b9', array('server' => '***', 'database' => '***', 'user' => '***', 'pass' => '***', 'prefix' => 'MyCMS-', 'codepage' => 'utf8'));
$__query1 = new \ML\SQL\Query($__driver0);
$__query1->sql = 'SELECT `tab`.`aa` as `tab.aa`, `tab`.`bb` as `tab.bb`, UCASE(`tab`.`xx`) as `Uxx` FROM  `MyCMS-MyTable` as `tab`  WHERE `tab`.`aa` LIKE \'sha%\' AND (`tab`.`bb` IS NULL OR `tab`.`bb`=2006) ORDER BY `tab`.`aa` DESC, `tab`.`bb` ASC';
$rows = $__query1->rows();

При этом мы можем использовать в запросе непосредственно переменные PHP (которые подставляются в запрос с защитой от SQL-инъекций в PHP и MySQL) и функцию условного формирования запроса _if. В частности такой код:

\ML::SQL(Select(
    'aa', _if($mode==1,'bb')
),		
from("MyTable")->as("tab"),
where( 
    field($fname) = $value
),
into($rows)->row());

скомпилируется в такой код:

$__driver0 = \ML\SQL::getDriver('c933f3523437d521bf59e9e6077255b9', array('server' => '***', 'database' => '***', 'user' => '***', 'pass' => '***', 'prefix' => 'MyCMS-', 'codepage' => 'utf8'));
$__query1 = new \ML\SQL\Query($__driver0);
$__query1->sql = 'SELECT `tab`.`aa` as `tab.aa`' . ($mode == 1 ? ', `tab`.`bb` as `tab.bb`' : '') . ' FROM  `MyCMS-MyTable` as `tab`  WHERE ' . $__driver0->getField('tab', $fname, '', '') . '=' . $__driver0->getValue($value);
$rows = $__query1->row();

Так как схема реализации простая, то решил не ограничиваться только SQL. Для этого в коде ищутся не конкретные команды \ML\SQL, а все вызовы из области имен \ML. Если такой вызов находится, то проверяется наличие класса \ML\<имя класса>. Если такой класс есть, то создается объект этого класса и вызывается его метод <имя класса>->compile(...), в который передаются лексемы текущего вызова (корневой узел, полученный от PHP-Parser) + сам объект этого класса. Это позволит использовать данный подход не только для обращения к БД, но и для других нужд (пока не придумал каких :)). В частности планирую сделать расширение ORM над SQL. Т.е. вызов \ML\ORM буду компилировать в код \ML\SQL, который уже будет компилироваться в код PHP. При этом так как время разбора выражения не важно, то при разборе ORM вызова можно потратить время на чтение информации о сущностях, их связях, полях и т.д.

P.S. Хотя прежде чем делать ORM, добавлю команду JOIN + команды UPDATE и DELETE.

Для тех, кому интересно, небольшой тестовый полигон для компиляции кода. Если найдете какую-то ошибку, пишите в комментариях. Пока это версия 0.1 alpha так что ошибки весьма вероятны.

UPD: 2017.02.03 Добавил побольше примеров
Поделиться с друзьями
-->

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


  1. andrewnester
    30.01.2017 09:43
    +15

    тому, что Вы изобрели, есть название — QueryBuilder — и их много


    1. shasoft
      30.01.2017 10:24
      -6

      QueryBuilder ничего не компилирует, а собирает запрос в runtime-е


      1. ilyaplot
        30.01.2017 10:28
        +2

        Это не так. Возьмите Doctrine и преобразуйте объект конструктора запросов в строку. Получите чистый SQL запрос.


        1. shasoft
          30.01.2017 11:43
          -3

          К примеру на Doctrine Builder записанное вот так

          $conn = DriverManager::getConnection(array(/*..*/));
          $queryBuilder = $conn->createQueryBuilder();
          $queryBuilder
              ->insert('users')
              ->setValue('name', '?')
              ->setValue('password', '?')
              ->setParameter(0, $username)
              ->setParameter(1, $password)
          ;
          

          у меня можно записать вот в таком виде.
          \ML::SQL(
          	insert('users'),
          	setValue('name',$username),
          	setValue('name',$username)
          );
          

          Пропала ненужная функция setParameter. А значит при добавлении нового поля нужно дописать одну строку, а не две.
          Т.е. вместо плейсхолдеров можно писать непосредственно их значения.
          Профит.


          1. voidMan
            30.01.2017 12:03
            +2

            Это не профит, это сомнительный костыль.


          1. Rastishka
            30.01.2017 13:27
            +2

            В Eloquent можно написать еще короче:


            DB::table('users')->
              insert([
                     'user' => $user.
                     'password' => $password
              ]);

            Ну и остальное так же лаконично, просто и хорошо документировано.


      1. andrewnester
        30.01.2017 10:31
        +2

        даже если так, то какой от этого плюс?


        1. abratko
          30.01.2017 11:43
          -1

          Например, можно писать запросы без привязки к именам таблиц в БД. Вместо таблиц имена классов сущностей.


          1. andrewnester
            30.01.2017 12:04
            +3

            ну в общем-то из почти любым QueryBuilder это можно сделать, это никак не связано с компилируемостью или нет.

            $qb->select(Entity::$tableName)->...
            


            Вуаля и Вы не привязаны к названию таблицы.


            1. abratko
              30.01.2017 16:41
              -2

              Да с компилируемостью не связано, я немного не вник в суть вопроса.

              По вашему примеру — не совсем вуаля, $tableName — должен существовать. А заставить его существовать никак нельзя.

              Можно заставить существовать только метод, как метод, определенный в интерфейсе.

              interface EntityInterface {
                 static function getTableName();
              }
              
              class MyEntity implements EntityInterface {
              //...
              }
              
              $qb->select(MyEntity::getTableName())->...
              


              Если использовать ту же Доктрину и его QueryBuilder, то такие конструкции не нужны.


        1. Nidhognit
          30.01.2017 13:05
          -2

          Я много пользуюсь Doctrine, могу описать следующие преимущества, которых у вас пока нет (или я их не увидел)
          1. Код в стиле DSL, что позволяет его делать более читабельным и очевидным, особенно для больших и сложных запросов.
          2. Безопасные запросы по умолчанию.
          3. Более быстрая работа за счет того что на проде все sql не компилируются, а берутся из кеша.
          4. Кеш самих результатов, без повторной отработки одних и тех же запросов.
          5. Возможность настройки нескольких уровней кешей.


          1. shasoft
            30.01.2017 14:36
            -2

            Пункты 1-3 вы просто не увидели.
            Пунктов 4-5 пока нет.


  1. mpakep
    30.01.2017 10:00
    -7

    В этом куске кода не закралась ошибка?
    Использование сравнения вместо присвоения все запутывает.

        _or(
            field('bb') == NULL,
            field('bb') == 2006
        )
    


  1. smple
    30.01.2017 10:13
    +8

    а что мешает просто по человечески использовать placeholder и биндить значения?

    зачем надо изобретать шаблонизатор, который разбивает текст на слова (парсер по сути) и на основе слов ищет классы и потом трансформирует текст, чтобы раставить placeholder и пробиндить value?

    Зачем так все усложнять?

    Если нужен сложный запрос проще использовать стандартные инструменты (простые), если операции с базой примитивные использовать что то типо AR, doctrine и тд


    1. shasoft
      30.01.2017 23:10

      Биндинг переменных после запроса выглядит не очень красиво. По моему мнению.
      AR и Doctrine тоже когда-то начиналиись с нуля.


      1. smple
        30.01.2017 23:24

        так в вашем же случае их тоже придется передавать и какая разница до или после или вовремя?

        просто большинство программистов знают sql и смогут понять его, а если и не знают полезно было бы узнать, а вот как ведет себя очередной query builder большая загадка.

        Я конечно люблю велосипеды и сам их пишу и полезно собирать feedback, но то к чему вы идете называется: dbal пишут как слой абстракции над базой данных (data base abstraction layer) и его ключевая полезность совместимость с различными БД (pgsql, oracle, sqlite, mysql ...) и способы хранения данных ограничены лишь количеством реализованных драйверов и нужно ли это проекту это большой вопрос (в моем опыте ни разу не пригодилось).

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


  1. ilyaplot
    30.01.2017 10:26
    +4

    Так как ORM слишком тяжеловесны для моих нужд

    В изучении? Первая строка вообще отбила желание читать дальше, но я продолжил.
    Попробуйте Doctrine. Вообще все query builders — это обертка над PDO.


  1. tzlom
    30.01.2017 12:05
    +5

    У вас много проблем:
    1 — где ссылка на репозиторий?
    2 — это MySQL, как быть с постгресом?
    3 — без join проект не нужен
    4 — есть кодогенерация, но как ей пользоваться?
    … итд итп


    1. shasoft
      30.01.2017 13:15

      1. данная статья ставила целью выяснить — насколько такой проект нужен людям. Чтобы знать: писать для себя, или для всех. Поэтому ссылки на код нет. Функционал завязан на куче моих библиотек, и в некоторых случаях из всей библиотеки тянется только одна-две функции. В случае «для всех» такие зависимости будут убираться.
      2. Нужно сделать драйвер и будет работать с постгресом. Просто исторически делаю на MySQL, поэтому драйвер написал только для него
      3. Это в процессе, без него этот проект не нужен даже мне :)
      4. Для работы с БД уметь пользоваться кодогенерацией не нужно. Она будет полезна для написания компиляторов своего кода. Статья не об этом, поэтому расписывать не стал.


      1. michael_vostrikov
        30.01.2017 19:53
        +1

        Простой QueryBuilder, который "собирает запрос в рантайме", пишется за полдня без всяких дополнительных библиотек. Думаю и работать будет быстрее, чем парсинг PHP + работа с файлами.


  1. jMas
    30.01.2017 12:33
    -2

    Все query-builder-ы делаются для того чтобы легко подменять драйвер базы данных с, например, MySQL на PostgreSQL. Если у вас такой задачи нет, значит достаточно пары функций, которые добавли бы удобства при биндинге параметров к PDO, и все.


    1. http3
      30.01.2017 21:21

      Действительно?
      То есть все запросы писать вручную? :)
      Шикарно. :)


      1. jMas
        30.01.2017 23:09
        +1

        Используя query builder код пишется автоматически?


        1. http3
          31.01.2017 00:37

          Хм, когда я пишу код, то стараюсь достичь какой-то автоматизации.
          В данном случае достигается автоматизация построения sql-запросов.

          Писать код ради кода, конечно же, не нужно.


          1. jMas
            31.01.2017 02:49

            По сути вы пишите такой же SQL код, только средствами PHP. Он практически буквально переводится в SQL. Профит разве что только в возможности более гибко подставлять названия таблиц/параметров или добавлять/убирать части SQL запроса в зависимости от контекста, а так же использовать драйвера для разиличных баз данных. Если всего этого не нужно, то почему не использовать просто SQL запросы? Почему так страшно написать чистый SQL запрос?


            1. http3
              31.01.2017 10:47
              -1

              Нет.
              Вы не видели мой query builder.
              А автор статьи пишет SQL на PHP. Поэтому я и написал.


              1. jMas
                31.01.2017 22:10
                +1

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


  1. http3
    30.01.2017 13:12
    +4

    А в чем смысл этой поделки?
    Это псевдопрограммирование. Такого псевдопрограммирования — навалом.


    1. shasoft
      30.01.2017 16:58
      -3

      Ваши доводы просто поражают доказательной базой.


  1. poxu
    30.01.2017 18:37
    +1

    То, что вы сделали, наверное, рано или поздно приходит в голову любому разработчику. Писать запросы не строками в sql, а непосредственно на языке, на котором ведётся разработка — по идее это должно быть прекрасно.
    Но на деле появляются следующие проблемы


    1. Разработчики знают sql и не знают вашей библиотеки
    2. Библиотека поддерживает не все возможности из sql
    3. Библиотека поддерживает не все возможности sql, специфичные для данной СУБД
    4. Читать sql проще, чем код, написанный с помощью библиотеки


    1. shasoft
      30.01.2017 19:24
      -4

      1. Любая библиотека не известна, пока её не изучишь.
      2. Мало какая библиотека поддерживает все возможности. Чтобы все возможности использовать нужно писать на низком уровне.
      3. Аналогично 2
      4. Тут не соглашусь. Само собой простой запрос без подстановки переменных в запрос выглядит просто. Сложности начинаются при использовании переменных и условных подстановок в запрос значений.

      На деле проблемы возникают всегда, даже при использовании чистого кода PHP для работы с БД. Библиотека писалась чтобы ускорить разработку, а также для возможности сменить БД не меняя пользовательский код


      1. poxu
        30.01.2017 19:38

        В том-то и проблема, что мало какая библиотека поддерживает все возможности. А экзотические возможности часто нужны. Это, как ни странно — серьёзная проблема.


        По поводу подстановки я не очень понял. Под подстановкой я обычно имею в виду что-то в духе:


        select
            *
        from
            users u
        where
            u.name = :userName

        вроде читаемо.


        1. shasoft
          30.01.2017 20:40
          -3

          Ясно о чем вы. SQL запрос читаемый. Но после запроса нужно еще делать биндинг всех параметров. Мне кажется, что проще в таком виде


          \ML::SQL(
               select(*),
               from('users')->as('u'),
               where( field('name')==$userName )
          )

          и никакого биндинга не нужно. Хотя, тут уже кому как


          1. poxu
            31.01.2017 08:43

            В вашем варианте запрос менее читаемый за счёт field. Разницы между $username и :username считай нет, единственное, что ваш вариант позволяет быстро найти в IDE место, где в переменную пишется значение. Но, при наличии IDE и мой вариант нормально обработается.


            Я так понимаю у вас запрос написан на php подобном языке, который компилируется в микс php и sql. И проблема, которую вы решаете формулируется как "на данный момент невозможно писать лёгкие запросы к БД на php".


            Теперь посмотрите на ситуацию с моей точки зрения. Если отбросить эстетические моменты, то чем хорош ваш подход?
            С моей точки зрения тем, что можно написать


            Select(
            'user_id',_if($nameRequired, 'name')
            ),  

            В подходе с чистым sql такое решается как-то так:


            'select ' .
                'user_id' .
                $nameRequired?',name':''

            Вариант с чистым sql примерно так же читаем, но необходимость ставить руками запятую перед name — бесит, необходимость писать пустые кавычки — бесит, да и сам тернартный оператор тоже не успокаивает. Кавычки и конкатенация опять же раздражают.


            Но избавление от всего этого не стоит того, чтобы переходить на новую библиотеку. Какая мне радость от того, что не надо писать кавычки вокруг строки, если перед каждым полем надо написать field('field_name')


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


            $u = new User();
            Select(
            $u->user_id,_if($nameRequired, $u->name)
            ),  
            From($u),
            Where(
                like($u->name, '%on')
            )

            Чтобы IDE подсказывала поля без дополнительных приседаний, чтобы можно было переименовывать поля не беспокоясь о значениях в строках, чтобы можно было найти все использования поля, включая те, что в запросах.
            И чтобы не было вот этой фигни field('field_name').
            Вот тогда бы пользы было много.


            1. shasoft
              31.01.2017 09:40

              >>чтобы не было вот этой фигни field('field_name').
              Самому не нравится. Но нужно как-то различать где названия полей, а где значение. Только из-за этого этот field и пишется. К примеру при использовании функций SQL писать field не обязательно, так как там и так понятно что, к примеру, в функции sum(arg1) параметр arg1 — это имя поля. А вот в условии WHERE имя поля может быть где угодно, поэтому используется field. Пока никак не могу придумать что-то удобное по этому поводу.


              1. poxu
                31.01.2017 10:18

                Самому не нравится. Но нужно как-то различать где названия полей, а где значение.

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


                Или, если применять подход, описанный мной выше, то


                $u = new User();
                $search = new Search();
                $search->topUserId = 2;
                Select(
                $u->user_id,_if($nameRequired, $u->name)
                ),  
                From($u),
                Where(
                     $search->topUserId > $u->user_id
                )

                Тут $u — экземпляр класса User, поэтому из него берутся названия полей, а $search это экземпляр класса Search, который не имеет отношения к схеме БД, поэтому из него берётся значение.


                1. shasoft
                  31.01.2017 10:42

                  При некоторых условиях поле может быть и слева и справа. И такие условия ломают схему «название поля всегда слева, а значения всегда справа». Как вариант — можно если поле справо, то писать его в field(). Хот в этом случае получится путаница — в одном случае нужно писать, в другом — нет.
                  Есть ещё идея вместо field(поле) писать в виде ~поле, это короче и вроде понятно. В общем нужно подумать или даже попробовать.

                  p.s.Еще есть сложность в понимании вот этого " $u — экземпляр класса User, поэтому из него берутся названия полей", так как текст PHP разбивается на лексемы. Если информация о том, что $u — это переменная, но какого она типа — в общем случае узнать невозможно.


                  1. poxu
                    31.01.2017 12:44

                    Тайп хинтинг же вроде был в php? Или он только для функций? Тогда может быть можно весь этот код в функцию обернуть с типизированными аргументами?


                    1. shasoft
                      31.01.2017 13:52

                      Честно говоря не понял о чем вы :)


                      1. poxu
                        31.01.2017 14:02

                        function query(User $u, Search $s) {
                        $search->topUserId = 2;
                        Select(
                        $u->user_id,_if($nameRequired, $u->name)
                        ),  
                        From($u),
                        Where(
                             $search->topUserId > $u->user_id
                        )
                        }

                        Вот от чём-то таком


                        1. shasoft
                          31.01.2017 14:52

                          В этом случае становятся недоступны переменные из контекста, откуда идет вызов. А смысл именно в этом — использовать текущие переменные в запросе.


                          1. poxu
                            03.02.2017 09:55

                            Переменные, используемые в запросе, в любом случае имеет смысл положить в объект $search или $searchCriteria или можно придумать любое другое интересное название для класса и переменной. И выделить для запроса отдельный метод (паттерн репозиторий).


                            Если же вы хотите непременно делать инлайн запросы, которые будут использоваться только в одном месте — по моему жертвовать понятностью запросов и их рефакторингопригодностью ради такого кейса — не очень правильно. Но, ваш парсер всегда может интерпретировать переменную как значение, если она не обёрнута в field и тип переменной вывести невозможно, и как поле таблицы, если field вналичии или тип переменной выводится как сущность.


                            Таким образом вы ничего не потеряете из вашего первоначального замысла и приобретёте описанные выше очень ценные фичи.


                            1. shasoft
                              03.02.2017 10:42

                              Весь смысл в том, чтобы использовать в запросе текущие переменные и не перекладывать их куда-то. Использование доп.объекта $search это то же самое что использовать функцию search() в моем синтаксисе. Я думаю что это лишнее.
                              НО, повторюсь, сначала мне нужно дописать работу SQL, потом уже буду ORM Настройку делать и тогда постараюсь учесть все что вы написали.


                              1. poxu
                                03.02.2017 11:40

                                Весь смысл в том, чтобы использовать в запросе текущие переменные и не перекладывать их куда-то.

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


                                НО, повторюсь, сначала мне нужно дописать работу SQL, потом уже буду ORM Настройку делать и тогда постараюсь учесть все что вы написали.

                                То, что я предлагаю не предполагает наличия ORM. Мне хотелось бы просто писать select на php, чтобы можно было пользоваться средствами IDE и не хардкодить названия полей, чтобы их можно было переименовывать.


                                1. shasoft
                                  03.02.2017 11:52

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


  1. iXCray
    30.01.2017 20:49

    Лол, я 3 года назад сделал zen-SQL, чтобы избавиться от этих всех сраных стрелочек и скобочек)
    https://gist.github.com/mrXCray/3bf126a6087e11cdf062


  1. funnybanana
    01.02.2017 06:08

    Тоже делал подобный QueryBuilder у меня это выглядит так:

    echo $sql->insert("table", array("id"=> "1", "name"=>"habr"))->send();
    // получаем mysqli_insert_id
    


    вместо array можно скормить и json
    Но я вот не стал свой велосипед на хабр тащить…


    1. shasoft
      01.02.2017 10:09

      У вас не такой. У вас просто Query Builder, который в runtime формирует запрос и выполняет его. В моем же случае идет компиляция запроса в PHP код аналогично Twig


      1. michael_vostrikov
        01.02.2017 19:11
        +1

        идет компиляция запроса в PHP код аналогично Twig

        Круто. А зачем?)


        1. shasoft
          01.02.2017 19:50
          -2

          Зачем это делает Twig? Чтобы сократить время работы.


          1. michael_vostrikov
            01.02.2017 21:22
            +2

            Twig переводит файл в разметке Twig в файл с кодом на PHP. Он заменяет разметку на код, который выполняется при ее обработке. И делает он это, чтобы не выполнять одну и ту же обработку каждый раз.
            Вы переводите строку с кодом на PHP в строку с другим кодом на PHP, причем обработка результата делается не в PHP, а в MySQL. Вы заменили операции со строками на операции с файловой системой (как минимум надо наличие скомпилированного файла проверить), а ввод-вывод практически всегда медленнее выполнения кода. Вы бы хоть производительность проверили, быстрее стало или нет.


            Похоже, вы пишете SQL-запросы в представлениях, так как файл компилируется и сохраняется под другим именем, а значит это не файл с логикой, иначе непонятно, как на него ссылаться через автозагрузку или include. Это неправильный подход, значит инструмент для упрощения этого подхода не будет востребован.
            Также SQL-код означает, что вы используете массивы, а не объекты. Это другой неправильный подход, так как ограничиваются многие полезные возможности. Вы поставили себе цель "сделать как в Twig", но не подумали, зачем это делается там и зачем это надо здесь. Поэтому и был мой вопрос)


            1. shasoft
              02.02.2017 11:34

              Дополнительная работа с файлами идет только при компиляции 1 раз. Т.е. есть класс с моей разметкой, при автозагрузке класса он ищется в директории компилированных классов, если не найден, то ищется в директории файлов для компиляции, компилируется и сохраняется в директорию компилированных файлов. После этого никакой дополнительной работы с файлом не будет. Если файлов много, то можно на продуктиве сделать предкомпиляцию всех классов. В этом случае в автозагрузке можно не проверять наличие файла, так как он гарантированно уже есть в директории компилируемых классов и сразу читать его оттуда.

              >>используете массивы, а не объекты
              Следующий шаг как раз и будет в том, чтобы использовать объекты. Также в join можно будет просто указывать сущность и компилятор будет сам находить поля для связи и добавлять их в компилированный код.

              Я поставил цель сделать как мне удобнее + как будет быстрее работать.


              1. michael_vostrikov
                02.02.2017 13:31

                при автозагрузке класса он ищется в директории компилированных классов

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


                После этого никакой дополнительной работы с файлом не будет.

                А если код в файле поменяется? Его же перекомпилировать надо.


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

                Это можно делать и без компиляции кода. И кстати. Если я в сущность добавлю поле, то в другом файле (где запрос) оно не появится, так как уже будет скомпилированная версия. Это надо при каждом изменении вручную перекомпилировать весь проект.
                А если класс сущности указан в переменной? Такое вообще сделать не получится. Еще одно ограничение.


                как будет быстрее работать.

                Вот я про это и говорил — "Вы бы хоть производительность проверили, быстрее стало или нет.". Где результаты хоть каких-нибудь тестов?


                1. shasoft
                  02.02.2017 13:57
                  -1

                  Вот я про это и говорил — «как минимум надо наличие скомпилированного файла проверить». Это тоже чтение с диска
                  Для таких случаев можно компилировать перед использованием. К примеру при инсталляции.
                  А если код в файле поменяется?
                  Код поменяется на разработке. При обновлении продуктива перекомпиляция сделает свое дело.
                  Это можно делать и без компиляции кода
                  Можно. Но это все занимает время. В моем же случае все делается один раз при компиляции.
                  Если я в сущность добавлю поле, то в другом файле (где запрос) оно не появится
                  Вы правите на разработке. При обновлении продуктива все заново перекомпилируется и все будет доступно
                  А если класс сущности указан в переменной?
                  Вот тут получается не очень хорошо, так как в этом случае придется определять связи в runtime Однако не думаю, что таких связок много встречается.
                  Где результаты хоть каких-нибудь тестов?
                  Сравнение с чем-то похожим займет много времени. Мой проект некомерческий, так что пока не готов потратиться на тесты. тем более что проект только начался, поэтому сравнение в любом случае будет некорректным.
                  Я же основываю свой вывод о «быстрее» на том факте, что, к примеру, быстрее на этапе компиляции определить связи двух сущностей в join и результат оформить в код, чем делать это каждый раз при формировании запроса. Само собой при условии статического задания имен сущностей

                  На всякий случай напишу ещё раз, потому что судя по комментариям (и не только вашим), у меня создается впечатление что считается, будто код будет компилироваться каждый раз при обращении к нему. Это не так. Компиляция будет происходить только один раз при первом обращении. (вариант — при установке/обновлении на продуктиве). На разработке перекомпиляция будет происходить каждый раз (чтобы сразу видеть изменения).


                  1. michael_vostrikov
                    02.02.2017 21:28

                    Если я в сущность добавлю поле, то в другом файле (где запрос) оно не появится

                    Вы правите на разработке

                    Я и говорю про разработку. Я добавил поле в одном файле. Запрос должен обновиться в другом, третьем и четвертом, где используется эта сущность. Компилятор это автоматически не отследит. Потому что другой, третий и четвертый файл не менялись, а значит перекомпилировать их не надо. Поэтому при каждом изменении каждой сущности надо перекомпилировать весь проект вручную.

                    Однако не думаю, что таких связок много встречается.

                    Достаточно много. Посмотрите как устроен ActiveRecord в Yii (про Doctrine уж молчу), и какие у него варианты использования. В compile-time многие возможности реализовать не получится.
                    Например:
                    // массив сущностей типа Comment
                    // запрос выполняется один раз при первом обращении
                    foreach ($user->comments as $comment) {
                        ...
                    }
                    
                    ...
                    
                    class User
                    {
                        ...
                        function getComments()
                        {
                            // имя класса передается в параметре
                            return $this->hasMany(Comment::class, ['user_id' => 'id']);
                        }
                        ...
                    }
                    


                    1. shasoft
                      02.02.2017 22:47
                      -1

                      Запрос должен обновиться в другом, третьем и четвертом, где используется эта сущность. Компилятор это автоматически не отследит.
                      В разработке скрипты ВСЕГДА компилируются. т.е. даже если файл находится в директории с компилированными файлами, то он все-равно компилируется. Это как раз и нужно чтобы избежать ситуации что вы описали.

                       return $this->hasMany(Comment::class, ['user_id' => 'id']);

                      Не очень понял, почему это не получится реализовать? В данном методе только статическая информация: имя класса Comment (если я правильно понимаю) и поля связки. Т.е. тут нет никаких значений, которые будут меняться и поэтому находятся в переменной. Comment::class будет неизменна (как минимум до обновления версии системы). Или я что-то не понял?


                      1. michael_vostrikov
                        03.02.2017 09:02

                        Comment::class передается в функцию, которая создает объект запроса. Если ее не вызывать, придется везде копировать ее код. А сами сущности создаются отдельно в методе __get. Это позволяет использовать конструкции вида $user->getComments()->where(['article_id' => $article->id])->limit(3)->all(). В compile-time вряд ли получится такое сделать. Ну и да, от конкатенации вы все равно не избавились, потому что $user->id известно только в runtime, а именованными параметрами вы почему-то пользоваться не хотите, судя по примерам.


                        1. shasoft
                          03.02.2017 09:24

                          В моем синтаксисе достаточно указать Comments и код автоматически скопируется. Да и описанную вами конструкцию можно будет использовать. Судя по всему вы не до конца поняли мою идею (возможно я просто не так объяснил).
                          Сложно приводить примеры по моему коду так как сейчас я на уровне SQL, а не на уровне ORM. В любом случае спасибо за комментарии.


  1. michael_vostrikov
    01.02.2017 19:10

    del