Потребовался класс для работы с БД. Мои требования были:
- Поддержка типов данных через макросы: строка, целое число, дробное число, логическое значение + возможность расширения
- Возможность компиляции запроса в PHP код (по аналогии как шаблонизаторы компилируют шаблон — к примеру так делает Twig)
- Возможность делать макросы для использования в запросе
Поискал по интернету — список чего нашел (самые лучшие на мой взгляд): safemysql, DbSimple, go-db, dibi, Yaff\db. Однако почему-то именно компилирующих нет. Поспрашивал на форумах и тостере, получил 10000 советов не писать велосипед, а изучить библиотеку X, потому что это круто и на ней можно сделать всё. И всё же… решил таки написать свой велосипед.
Я не буду в очередной раз писать про защиту от SQL инъекция. Если кто еще не читал, то вот отличная статья на эту тему Защита от SQL-инъекций в PHP и MySQL от автора safemysql. Данная статья — это моё ТЗ (не работающий класс, а только его описание) для написание работающего класса, который можно использовать в своем проекте. Возможно собранная информация будет и еще кому-то полезна. Для меня же польза будет в том, что при описании для других людей лучше обдумывается тема, что способствует лучшему продумыванию архитектуры.
Базовые макросы
Все макросы будут иметь вид ?<имя макроса>(параметры). Я написал макросЫ и их действительно будет несколько — целых 2 штуки. Перечислим их:
- ?if(имя переменной){текст запроса, который должен выполняться в случае если указанная переменная ИСТИНА}[?else{текст запроса, который должен выполняться в случае если указанная переменная ЛОЖЬ}] — этот макрос служит для формирования сложных запросов в зависимости от условия. Блок else является не обязательным.
- ?_dot(текст для вставки в запрос) — этот макрос служит для вставки в запрос переменных или вызовов функций. Макрос с нижним подчеркиванием, так как он предназначен не для вызова из текста запроса, а для формирования макросов расширения, речь о которых пойдет далее.
Рассмотрим на примере. Пусть есть запрос вида
SELECT name FROM users WHERE age > ?_dot(intval($vars['age'])) AND ?if(male){gender='1'}?else{gender = '2'}
в результате обработки получим следующий PHP код для формирования запроса
$sql = "SELECT name FROM users WHERE age > ".intval($vars['age'])." AND "; if($vars['male']) $sql .= "gender='1'"; else $sql .= "gender='2'";
Все остальные макросы, которые могут потребоваться (в том числе для безопасной вставки переменных в текст запроса), можно сформировать с помощью 2-х приведенных выше макросов. При этом также важен тот факт, что все переданные переменные содержатся в массиве $vars. В примере выше используются две переменные из этого массива: age и male.
Архитектура классов
Каждый вариант будет располагаться в папке производитель\вариант. Базовый вариант будет находиться в папке Shasoft\Base. Также классы каждого варианта должны располагаться в аналогичном пространстве имен: базовый вариант будет находиться в пространстве имен Shasoft\Base. Такой подход позволит избежать пересечения имен классов в разных вариантах.
Основной класс
Основной класс для работы с БД — SafeSql. В базовом варианте он будет содержать только метод query, который будет вызываться для выполнения запросов.
$db->query("текст запроса",<массив переменных>,function($row,$qi) { });
- $row — очередная строка выбранных данных или неопределенное значение если запрос не подразумевает возврат данных (INSERT, UPDATE, DELETE)
- $qi — дополнительная информация. Содержит следующие поля
- effected_rows — число строк, затронутых предыдущей операцией MySQL
- error — строка с описанием последней ошибки (или false если ошибки не было)
- insert_id — автоматически генерируемый ID
Также функция должна прерывать перебор данных, для чего достаточно вернуть из неё значение false.
Этот класс должен переопределяться в обязательном порядке в каждом новом варианте даже если не добавляется никакого нового функционала. Также класс содержит переменную opts с параметрами, переданными в класс при создании и указатель на объект драйвера, созданного по имени драйвера. переданного в переменной driver. Речь о классе драйвера пойдет далее.
Класс макроса
Как уже писал ранее — есть всего два базовых макроса. Все остальные макросы, которые могут понадобиться, можно создать с помощью макросов расширения. В тексте запроса макрос имеет следующий вид
?<любое имя>[(параметры)] — любой определенный вами макрос. Для реализации этого макроса необходимо в папке вариант\Macros создать файл с <именем макроса>.php и определить в нем класс с именем макроса, который должен наследоваться или от абстрактного класса Shasoft\Base\Macro или от ранее определенного макроса. Для реализации замены макроса необходимо реализовать абстрактный метод sql()
//! Абстрактный класс макроса abstract class Macro { // Параметры protected $opts; // Драйвер protected $driver; // Конструктор function __construct() { } // Выполнить преобразование // $args - строка параметров abstract public function sql($args); // Разобрать параметры макроса на список параметров // $args - строка параметров static public function parseArgs($args) {...} }
Если необходимо разобрать строку параметров макроса на список параметров, то можно воспользоваться методом parseArgs(). Данная функция возвращает массив аргументов, т.е. массив объектов класса Shasoft\Base\MacroArg
//! Класс аргумента макроса class MacroArg { // Значение protected $value; // Символ строки (если = false, значит это не строка) protected $ch_string; // Текст public getText() { return $this->value; } // Это строка? public isString() { return $this->ch_string==false ? false : true; } // Получить текст строки как константу public getConstString() { return $this->ch_string.addslashes($this->value).$this->ch_string; } }
Метод sql может возвращать текст, который содержит другие макросы.
Если макрос был определен в одном из родительских классов, то вы можете его заменить в текущей реализации. Если макроса нет в текущей реализации, то макрос с таким именем ищется в родительском классе, затем в родительском родителя и т.д. Именно для этого необходимо обязательно определять основной класс SafeSql для вашего варианта — необходим список родителей класса, чтобы знать где искать не определенные в данном варианте макросы.
Класс драйвера
Данный класс будет отвечать за работу с конкретной БД. Класс также можно будет расширять от версии к версии. В базовой версии драйверу необходимо реализовать всего три функции: открытие соединение с БД, закрытие соединение, генерация PHP кода для выполнения запроса.
<?php namespace Shasoft\SafeSql; //! Абстрактный класс драйвера для работы с БД abstract class Driver { // Конструктор function __construct() {} // Открыть соединение с БД // result - возвращает true или текст ошибки abstract public function connect($opts); // Закрыть соединение с БД abstract public function close($opts); // Сформировать код PHP для выполнения запроса // $varSql - имя переменной содержащей запрос (не текст запроса, а имя ПЕРЕМЕННОЙ, которая этот запрос содержит) // result - возвращает текст PHP кода abstract public function sql($varSql); } ?>
Если в варианте нет драйвера, то он ищется по аналогии с макросом: в родительском варианте, если нет, то в родительском родительского и т.д.
Параметры базового варианта
Опишем параметры базового варианта.
- driver — имя драйвера. Короткое имя, т.е. без приставки Vendor\Variant\Drivers
- server — сервер. По умолчанию «localhost»
- database — имя базы данных
- user — имя пользователя
- pass — пароль
А теперь напишу ка я базовый класс на основе этой статьи и станет ясно, насколько реально то, что я описал. За работу!
ссылка на оригинал статьи http://habrahabr.ru/post/263679/
Добавить комментарий