И всё-таки, как «правильно» выполнять из PHP сложные sql-запросы?
Года три назад попросили меня написать простенькую софтинку на PHP для печати счетов. Так уж получилось, что логика СУБД (MSSQL), на которую опиралась эта софтина, была весьма монструозна — около восьмиста таблиц, перепутанных взаимными связями. Поэтому большинство запросов включали в себя условные вызовы аггрегирующих функций (самое простое — sum(when eq then a else 0 end)), PIVOT\UNPIVOT и тому подобное.
C учетом моего не слишком уж большого опыта на тот момент я решил не ввязываться в data mapping\active record и просто закинул запросы в текстовые файлы, сделал в них текстовые плейсхолдеры типа {PARAM1} и написал функцию, заменяющую текст плейсхолдера на значения переменных из ассоциативного массива.
С тех пор прошло много времени — софтинка обзавелась аджаксовой мордочкой, мигрировала на мой любимый CI, обзавелась двумя десятками модулей, содержащих всякую аналитику. Неизменным оставался лишь способ вызова SQL-запросов — ассоциативный массив, текстовый файл, плейсхолдеры.
И, что удивительно, всё это безобразие работает и по сей день. Правда, количество костылей и подпорок уже начинает слегка раздражать.
А именно — ручной escaping, type-checking, в конце концов — не работает ctrl+пробел. Я уж промолчу про то, что отладка запросов нормально не работает из-за «неродных» плейсхолдеров, и syntax check в PhpStorm из-за этого сходит с ума. А вместе с ним и я, потихоньку.
Doctrine на такой сложности запросов превращает двухстрочный вызов в нечто невероятное, а про размер схемы я просто тактично умолчу. Родные плейсхолдеры (:PARAM) не умеют содержать в себе куски запроса. А у нас автогенерация во все поля, кастомные фильтры, отключаемые джойны из внешних СУБД, находящихся за 4-5 тыс км от нашей серверной комнаты. Паника, боль, унижение.
В общем, родился план. Делаем класс Query с тайп-чекингом, возвратом результатов в XML (да, есть и такое) либо в массиве. Дальше делаем кучу наследников для каждого отдельного SQL-запроса, у которых в public -секции висит класс с перечисленными входными параметрами. А дальше всё так же — текстовый файл и плейсхолдеры.
Обратно — либо массивом, либо XML, либо неким классом наподобие ResultSet->First (next,last).
Всё это до боли напоминает изобретение велосипеда. Подозреваю, что всё это уже написано до нас. Но беглый гуглеж не помог. Может, кто сталкивался с такими вещами?
Не нужно перебарщивать, решите только одну главную проблему передачи параметров — escaping и quoting параметров, никаких монструозных велосипедов, только поправьте метод выполняющий ваши запросы, :param пусть идут в sql-запрос без изменений (а сам запрос сделать параметризованным, список параметров получается просто поиском всех вхождений /\:(\w+)/), а {param} заменяются по старинке, пусть там останутся куски самого запроса с выражениями.