Класс для работы с MySQL компилирующий запрос для выполнения в чистый PHP код

от автора

Потребовался класс для работы с БД. Мои требования были:

  1. Поддержка типов данных через макросы: строка, целое число, дробное число, логическое значение + возможность расширения
  2. Возможность компиляции запроса в PHP код (по аналогии как шаблонизаторы компилируют шаблон — к примеру так делает Twig)
  3. Возможность делать макросы для использования в запросе

Поискал по интернету — список чего нашел (самые лучшие на мой взгляд): 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()

Абстрактный класс макроса Shasoft\Base\Macro

//! Абстрактный класс макроса 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

Класс 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 кода для выполнения запроса.

Интерфейс драйвера Shasoft\Base\IDriver

<?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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *