В данной статье хотелось бы рассказать об одной интересной проблеме, с которой мне пришлось столкнуться. Необходимо было автоматизировать процесс подстановки большого количества именных параметров в 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 запроса, тем самым сократив код и время, затрачиваемое на разработку. Разумеется, решение применимо к любым запросам с именными параметрами.
Главным минусом является то, что имена элементов формы должны в точности соответствовать именам параметров, хотя я считаю, что, возможно, так и должно быть, поскольку это упрощает проектирование.
Вероятно, мое решение не является оптимальным. Применительно к данной проблеме, я даже уверен, что существуют более эффективные и удобные способы добиться похожего результата, однако, оно помогло мне сэкономить несколько часов, которые позже были потрачены на долгожданный сон. Надеюсь оно принесет пользу и вам.
Автор: authoris