Параметраризированные запросы в PHP и возврат массива из результата

ru-vadik

Новичок
Параметраризированные запросы в PHP и возврат массива из результата

Написал вот такую ф-цию:
PHP:
function dbGetDataTable($query)
{
	$link = mysqli_connect(dbHost, dbUser, dbPassword, dbBase);
	if (mysqli_connect_errno())
	{
	   printf("Подключение невозможно: %s\n", mysqli_connect_error());
	   exit;
	}

	//$query = mysqli_real_escape_string($link, $query);
	$result = mysqli_query($link, $query);
	$dataTable = array ();
	while ($row = mysqli_fetch_array($result, MYSQLI_BOTH))
	{
		array_push($dataTable, $row);
	}
	mysqli_free_result($result);
	mysqli_close($link);
	return $dataTable;
}
Можно ли написать такую же ф-цию с параметраризированным запросом, при условии, что, ни кол-во параметров в запросе, ни кол-во возвращаемых столбцов заранее не известно. Например, с сигнатурой
PHP:
function dbGetDataTable($query, $params)
{
    ...
}
 

Fortop

Новичок
Можно ли написать такую же ф-цию с параметраризированным запросом, при условии, что, ни кол-во параметров в запросе, ни кол-во возвращаемых столбцов заранее не известно
Можно.
 

ru-vadik

Новичок
Автор оригинала: *****
...но ход ваших мыслей, Марьванна, мне понравился, хы хы
Это ф-ция для вызова хранимой процедуры, поэтому экранирование там не нужно.

-~{}~ 20.04.10 17:29:

Автор оригинала: Fortop
Можно.
Будьте добры, пример.
 

zerkms

TDD infected
Команда форума
Fortop
а типы брать откуда?
перед передачей явно кастовать - слишком сложно.
всё считать строками - не наш метод
указывать в том же массиве параметров - угу, разве что так
 

Фанат

oncle terrible
Команда форума
Это ф-ция для вызова хранимой процедуры, поэтому экранирование там не нужно.
Oh, yeah baby!
если бы это был обычный запрос, его нужно было экранировать, хы хы

по поводу параметров - копать в сторону function_num_args

А вообще вопрос интересный. Если параметры именованные, то как их сопоставлять? Чисто по порядку общей очереди?
 

zerkms

TDD infected
Команда форума
*****
тогда ещё и парсить запрос придётся... при таком подходе только индексированные параметры + хер знает откуда брать их типы.
 

dimagolov

Новичок
при таком подходе только индексированные параметры + хер знает откуда брать их типы.
что мешает ставить плейсхолдеры типа %i %s %d, etc? и типы имеем и ожидаемое кол-во параметров
 

Фанат

oncle terrible
Команда форума
нафига тогда препареды вообще сдались? Тогда уж самопал типа котеровского делать :)

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

-~{}~ 20.04.10 18:12:

а, дошло.

короче, единственный кошерный вариант - это передавать функции уже препарированный запрос с забинденными данными.
а точнее - статемент на экзекуцию.

то есть, делать ровно то же самое, что и в случае с классическим запросом:
сначала слешим данные
потом подставляем в запрос
потом передаем готовый запрос в функцию.

так и здесь
ы?
 

Fortop

Новичок
zerkms
а типы брать откуда?
перед передачей явно кастовать - слишком сложно.
всё считать строками - не наш метод
Откровенно говоря типов всего-то два "типа", те, которые надо заключать в кавычки и те, которые не надо.
Но в большинстве случаев я бы не геморроился, и откавычивал все. Как вариант вместо многомерного массива - использовать массив каких-нибудь DTO, которые знают какого типа у него поля и отдают их уже приведенными.

DTO-шки же генерятся автоматом по структуре БД.

-~{}~ 20.04.10 18:11:

ru-vadik
Будьте добры, пример.
Пример чего?

Получаем запрос, массив параметров.
Если параметры будут передаваться не в массиве, то воспользоваться этим советом
*****
копать в сторону function_num_args
Дальше делаем [m]mysqli-stmt.prepare[/m] запросу и в цикле [m]mysqli-stmt.bind_param[/m] параметрам.
После чего [m]mysqli-stmt.execute[/m] и стандартная обработка результата.
 

Mols

Новичок
Ну вообщето в ХП указывается тип принимаемых параметров. Так что про тип можно сильно и не думать. Кроме того, в тех же ХП фиксированное число этих параметров, и фиксированных порядок. Поэтому вполне можно делать
PHP:
executeProcedure($procedureName, $arrParam)
Порядок и кол-во аргументов в arrParam должны соответствовать порядку и количеству того, что ожидает процедура.
Эскейпить можно всё кроме нулл. ХП сама разберётся из своей декларации.
 

ru-vadik

Новичок
С хранимыми процедурами вообще можно не заморачиваться. Передавать "CALL ..." и все, инъекций все равно не будет.

Просто хотелось написать три функции, через которые шла бы вся работа с MySql. вот что получилось:
PHP:
<?php
    define(dbHost, "127.0.0.1");
    define(dbUser, "root");
    define(dbPassword, "123");
    define(dbBase, "test");

    function dbGetDataTable($query)
    {
        $link = mysqli_connect(dbHost, dbUser, dbPassword, dbBase);
        if (mysqli_connect_errno())
        {
           printf("Подключение невозможно: %s\n", mysqli_connect_error());
           exit;
        }

        //$query = mysqli_real_escape_string($link, $query);
        $result = mysqli_query($link, $query);
        $dataTable = array ();
        while ($row = mysqli_fetch_array($result, MYSQLI_BOTH))
        {
            array_push($dataTable, $row);
        }
        mysqli_free_result($result);
        mysqli_close($link);
        return $dataTable;
    }

    function dbGetScalar($query)
    {
        $link = mysqli_connect(dbHost, dbUser, dbPassword, dbBase);
        if (mysqli_connect_errno())
        {
           printf("Подключение невозможно: %s\n", mysqli_connect_error());
           exit;
        }

        //$query = mysqli_real_escape_string($link, $query);
        $result = mysqli_query($link, $query);
        if($row = mysqli_fetch_array($result, MYSQLI_BOTH))
        {
            $result = $row[0];
        }
        mysqli_free_result($result);
        mysqli_close($link);
        return $result;
    }

    function dbExecuteNonQuery($query)
    {
        $link = mysqli_connect(dbHost, dbUser, dbPassword, dbBase);
        if (mysqli_connect_errno())
        {
           printf("Подключение невозможно: %s\n", mysqli_connect_error());
           exit;
        }

        //$query = mysqli_real_escape_string($link, $query);
        $result = mysqli_query($link, $query);
        mysqli_free_result($result);
        mysqli_close($link);
        return $result;
    }    
?>
Это модуль рассчитан для вызова хранимок. Однако, выяснилось, что не все программисты понимают, что это такое :). И поэтому потребовалось написать аналогичный модуль для параметраризированных запросов.

Сделать я его не смог. Обычно я программирую на .NET и Java, а там такие вещи делаются очень просто.

Вот что я набыдлокодил:
PHP:
function dbGetDataTable()
{
	$numargs = func_num_args();
	if($numargs < 1)
	{
		printf("Неверное количество параметров.");
		exit();
	}
	$link = mysqli_connect(dbHost, dbUser, dbPassword, dbBase);
	if (mysqli_connect_errno())
	{
	   printf("Подключение невозможно: %s\n", mysqli_connect_error());
	   exit();
	}

	$query = func_get_arg(0);
	if ($stmt = mysqli_prepare($link, $query))
	{
		if($numargs > 1)
		{
			$start_params = 0;
			while($numargs - $start_params >= 2)
			{
				$type = func_get_arg($start_params + 1);
				$value = func_get_arg($start_params + 2);
				mysqli_bind_param($stmt, $type, $value);
				$start_params += 2;
			}
		}
		mysqli_execute($stmt);
		
		$dataTable = array();
		mysqli_stmt_bind_result($stmt, $data);

		while (mysqli_stmt_fetch($stmt))
		{
			array_push($dataTable, $data);
		}
		mysqli_stmt_close($stmt);
	}
	mysqli_close($link);
	return $dataTable;
}
Вызов функции:
PHP:
$dataTable = dbGetDataTable("SELECT ? FROM Dual UNION SELECT ? FROM Dual", "d", 123, "s", "abc");
Ошибок не выдается, но и в while я не попадаю, параметры все-таки вставляются неверно.
Еще есть проблема с функцией mysqli_bind_param: не удалось сделать так, чтобы она не скалярное значение в переменную присваивала, в возвращала строку массива.

В любом случае, спасибо всем откликнувшимся. Программисты будут учить хранимые процедуры :).
 

Fortop

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

mysqli_bind_param($stmt, $type, $value);
биндится условно говоря по ссылке - имени переменной.
поэтому если делать так, то все параметры будут одинаковыми.

надо
PHP:
mysqli_bind_param($stmt, $type, &$value);
 

zerkms

TDD infected
Команда форума
Откровенно говоря типов всего-то два "типа", те, которые надо заключать в кавычки и те, которые не надо.
"типов" два, верно, вот только трактовать их нужно иначе: те, которые нужно заключать в кавычки, и те, которые нужно приводить к какому-то конкретному типу: float, int. так что не всё так просто.
 

Fortop

Новичок
которые нужно приводить к какому-то конкретному типу: float, int
Делать валидацию на этапе формы? не?
А дальше меня уже ничего потом не интересует, я просто либо заключаю в кавычки, либо нет.

Вопрос локали для чисел меня тоже не животрепещет.
 

Fortop

Новичок
итого логика sanitize'ации запроса разбивается на 2 части ))
Да хоть на 15ть.

Кавычки в запросе и mysql_real_escape_string не имеет никакого отношения к валидации. А валидация достаточно часто нужна и без записи в БД. У вас это все в одном месте?
 

zerkms

TDD infected
Команда форума
Fortop
Доверие к данным, которые должны были быть провалидированными, но на факте валидация была или недостаточная или вообще опущена, что в итоге приведёт к добавлению в запрос данных as-is, не заключая в кавычки и не применяя специальных функций - в итоге может привести сами знаете к чему.
 

Fortop

Новичок
но на факте валидация была или недостаточная или вообще опущена
Эм, т.е. вопрос кривых рук?

Ну делайте как в космосе - тройное дублирование валидации. Кто же Вам мешает то?

-~{}~ 21.04.10 14:03:

Угу, сейчас выяснится что и дублирование не будет или будет криво сделано... и программа будет написана лапшой...

Что я могу сделать для Вас в таком случае? :)
Не пишите так и все будет нормально.
 
Сверху