Автоматическая генерация параметров для PDO

от автора

В данной статье хотелось бы рассказать об одной интересной проблеме, с которой мне пришлось столкнуться. Необходимо было автоматизировать процесс подстановки большого количества именных параметров в SQL запросы типа INSERT и UPDATE, то есть избавится от наводнивших проект конструкций типа:

$paramsArray[‘fname’] = $_POST[‘fname’]; $paramsArray[‘sname’] = $_POST[‘sname’]; 

Всех, кому интересно, как я решил такую задачу, приглашаю под кат.

Для начала стоит рассказать немного о PDO.
Дело в том, что я стал использовать PDO в своих проектах не так давно. Положительных эмоций была масса. Во-первых, практически не приходилось больше самому заботится о «чистке» передаваемых параметров: все, что могло нанести вред базе, благополучно отметалось при подготовке запроса. Во-вторых сменить СУБД, чаще всего, можно было заменой одной единственной строки. Плюс ко всему, мы имеем возможность использовать сразу несколько СУБД, что в некоторых случаях может быть полезно. В-третьих, параметры легко передавать: поместили все в массив и — готово. Можно использовать как безымянные, на манер JDBC, так и именные параметры.
Пример:

require_once('config.php'); $dbc = new PDO("mysql:host=".HOST.";dbname=".DATA_BASE_NAME.";charset=utf8", USER, PASSWORD);  #Для безымянных $sqlString = "INSERT INTO table1(`name`, `text`, `date`) VALUES (?, ?, ?)"; $params = array("First Post", "Hello World", "01/01/2013"); $statment = $dbc->prepare($sqlString); $statment->execute($params);  #Для именных $sqlString = "INSERT INTO table1(`name`, `text`, `date`) VALUES (:name, :text, :date)";  $params = array("name" => "Second Post", "text" => "Say Hello", "date" => "01/02/2013");  $statment = $dbc->prepare($sqlString); $statment->execute($params); 

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

$params = array("text" => "Habrahabr", "name" => "Third Post", "date" => "01/03/2013"); $statment->execute($params); 

Итак, соберем все в единое целое и напишем немного кода.

class DB {     static private $dbc         static public function dbc () {         	try {         	require_once('config.php');             self::$dbc = new PDO("mysql:host=".HOST.";dbname=".DATA_BASE_NAME.";charset=utf8", USER, PASSWORD); 			} catch(PDOException $exc) { 				Logger::setLog($exc->getMessage()); 				return false; 			} 		} 		static public function insert($sqlString, $object) { 			if (!isset(self::$dbc)) {                 self::dbc();             }             try{                 $statment = self::$dbc->prepare($sqlString);                 $statment->execute((array)$object);                 return self::$dbc->lastInsertId();             } catch (PDOException $exc) {             	Logger::setLog($exc->getMessage());                 return false;             } 		} } 

Первый метод создает новое подключение. Второй выполняет запросы INSERT. Если подключение не было создано, создает его. PDO умеет выбрасывать исключения, так что желательно их перехватить. Можно передавать как массивы, так и объекты. Лично я чаще предпочитаю вторые.

За обработку форм назначим класс PostController.

class PostController {     const INSERT = "INSERT INTO `posts` (`name`, `text`, `date`) VALUES (:name, :text, :date)";          public static function post(){         if (isset($_POST["new_post"])) {             $insertObject = NULL;             $insertObject->name = $_POST["name"];             $insertObject->text = $_POST["text"];             $insertObject->date = $_POST["date"];             DB::insert(self::INSERT, $insertObject);         }     } } 

А теперь представьте, что у нас достаточно много форм, или таблицы состоят из 10 — 20 полей. А если и то и другое? Записи вида $insertObject->name = $_POST[«name»]; будут захламлять объемную часть кода. Если честно, первая идея, которая мне пришла в голову, была написать нечто подобное:

DB::insert(self::INSERT, $_POST); 

Конечно, необходимо чтобы имена элементов формы соответствовали именам параметров (может оно и к лучшему?). «Пусть PDO сам найдет и подставит необходимые параметры», — так я думал и ошибался. Как оказалось, число элементов массива должно быть равным числу параметров, иначе PDO выкинет исключение. Временным решением стало удаление не интересующих меня данных. Например так:

class PostController {     const INSERT = "INSERT INTO `posts` (`name`, `text`, `date`) VALUES (:name, :text, :date)";          public static function post(){         if (isset($_POST["new_post"])) {             $insertArray = $_POST;             unset($insertArray["new_post"]);             DB::insert(self::INSERT, $insertArray);         }     } } 

Решение действительно работало, код сократился, хотя и стал менее понятным. Однако, в этом году в рамках курсовой работы мне пришлось трудиться над одним WEB сервисом для университета. Проблема заключалась в том, что была одна огромная форма с множеством элементов. В базе было много таблиц, каждая из которых, в свою очередь, содержала множество полей. Выбор таблиц и полей для вставки зависел от махинаций на форме. И первый, и второй способы требовали написания солидных кусков кода, чего из-за ограниченности сроков делать совершенно не хотелось. Спустя 10 минут размышлений в голову пришел один метод, который бы, разбирая строку запроса SQL, сам находил данные для параметров, сопоставлял их, и возвращал готовый объект для вставки.

class PostController {     const INSERT = "INSERT INTO `posts` (`name`, `text`, `date`) VALUES (:name, :text, :date)";      #Шаблон поиска параметров.     #Если кто-то использует и другие символы, можно их легко добавить     const PATTERN = "|:(([-A-Za-z1-9_])*?)|U"; 	public static function findObjectInPost($queryString) { 		$fields = array(); 		$postObject = NULL; 		preg_match_all(self::PATTERN, $queryString, $fields); 		foreach ($fields[1] as $value) { 			if (isset($_POST[$value])) { 				$sender = $_POST[$value]; 				if (strcasecmp($sender, "") == 0) { 					$sender = NULL; 				} 				$postObject->$value = $sender; 			} 		} 		return $postObject; 	}          public static function post(){         if (isset($_POST["new_post"])) {             unset($_POST["new_post"]);             DB::insert(self::INSERT, $_POST);         }     } } 

Суть метода findObjectInPost в следующем: регулярным выражением находим параметры, перебираем полученные параметры, ищем совпадения в массиве $_POST, если такой элемент будет найден, добавляем его, как новое поле объекта, данные записываем в это поле.

В результате получаем быструю обработку запроса в одну строку:

    public static function post(){         if (isset($_POST["new_post"])) {             DB::insert(self::INSERT_POST, $insertObject = self::findObjectInPost(self::INSERT_POST));         }             if (isset($_POST["new_company"])) {             DB::insert(self::INSERT_COMPANY, $insertObject = self::findObjectInPost(self::INSERT_COMPANY));         }            if (isset($_POST["new_author"])) {             DB::insert(self::INSERT_AUTHOR, $insertObject = self::findObjectInPost(self::INSERT_AUTHOR));         }     } 

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

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

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

ссылка на оригинал статьи http://habrahabr.ru/post/168479/


Комментарии

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

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