Нужны советы по созданию класса, который обрабатывает операции запроса sql с заданным массивом (имея полный контроль над массивом и получение информации о записи). Мой код работает правильно (это просто пример), но нужно проверить его безопасность, или есть лучший способ достичь результата. Хочу отметить, что все опасные символы перед добавлением в массив очищаются другой функцией (для предотвращения инъекции). Итак, основная идея — создать массив со структурой запроса, затем перейти к функции и выполнить операцию запроса sql. Спасибо, ценю все ответы.
**
ЭТО ТОЛЬКО ДЛЯ УЧЕБНЫХ ЦЕЛЕЙ
**
Создан класс, обрабатывающий операции:
class my_op
{
private $pdo;
public function __construct($pdo)
{
$this->pdo = $pdo;
}
private $safe_code = "@@@";
private function safe_chars($first_string, $second_string)
{
$safe_chars = array(
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"0",
",",
"'",
"?",
"!",
" ",
"_",
"-",
":",
"(",
")",
"="
);
$unsafe_count = null;
if (strlen($first_string) > 0):
foreach (str_split($first_string) as $char_check)
{
if (!in_array($char_check, $safe_chars))
{
$unsafe_count++;
}
}
endif;
if (strlen($second_string) > 0):
foreach (str_split($second_string) as $char_check)
{
if (!in_array($char_check, $safe_chars))
{
$unsafe_count++;
}
}
endif;
return $unsafe_count;
}
private function build_query($my_array)
{
$structure_allowed = array(
"UPDATE",
"DELETE FROM",
"INSERT INTO",
"WHERE",
"SET",
"VALUES",
"ORDER BY"
);
$allowed_arrays = 3;
$K_LEAST = 1;
$K_MOST = 4;
$KEY_S = "STRUCTURE";
$KEY_V = "SETPARAM";
if (is_array($my_array) && sizeof($my_array) > 0):
$secure_purposes = implode('|', $structure_allowed);
$filtered_array_my_op = filter_var_array($my_array, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH);
$ats = false;
try
{
if (sizeof($my_array) <= $allowed_arrays):
$op_made = null;
$op_chars = null;
$op_stop = null;
foreach (is_array($filtered_array_my_op) || is_object($filtered_array_my_op) ? $filtered_array_my_op : array() as $splited_array)
{
$parsed_values = array();
$parsed_structure = array();
$s = null;
$v = null;
$param_list = null;
$values_list = null;
$f_mode = null;
foreach ($splited_array as $parsed_keys_g => &$parsed_values_g)
{
foreach ($parsed_values_g as $direct_key => &$direct_value)
{
switch ($parsed_keys_g)
{
case "STRUCTURE":
if ($parsed_keys_g == $KEY_S && strlen($direct_value) > 0 && in_array($direct_key, $structure_allowed)):
if ($this->safe_chars($direct_key, $direct_value) <= 0):
$parsed_structure[$direct_key] = $direct_value;
$param_list .= "$direct_key $direct_value";
$s++;
else:
$op_chars++;
endif;
elseif ($parsed_keys_g == $KEY_S && strlen($parsed_s_value) < 0 || $parsed_keys_g == $KEY_S && !in_array($parsed_s_key, $structure_allowed)):
$op_stop++;
endif;
break;
case "SETPARAM":
if ($parsed_keys_g == $KEY_V && strlen($direct_value) > 0):
if ($this->safe_chars($direct_key, $direct_value) <= 0):
$parsed_values[$direct_key] = $direct_value;
$values_list .= "$direct_key $direct_value ";
$v++;
else:
$op_chars++;
endif;
elseif ($parsed_keys_g == $KEY_V && strlen($direct_value) < 0):
$op_stop++;
endif;
break;
case "FMODE":
$f_mode = $direct_value;
break;
}
}
}
$op_made++;
if ($s >= $K_LEAST && $s <= $K_MOST && $v > 0 && preg_match("($secure_purposes)", $param_list) && array_key_exists($KEY_S, $splited_array) && $op_stop <= 0):
try
{
// $this->pdo->beginTransaction();
// $my_operation = $this->pdo->prepare("$param_list");
// $bindcount = 0;
// foreach ($parsed_values as $value_key_parsed => $value_parsed) {
// $bindcount++;
//$my_operation->bindValue($value_key_parsed, $value_parsed);
//}
//switch ($f_mode) {
//case "execute":
//$my_operation->execute($parsed_values);
//break;
//}
//if ($my_operation->execute($params)) {
$ats .= "$f_mode Success #$op_made #$bindcount on //params($s) values($v) // PARAMLIST: " . $param_list . "//<BR> VALUESLIST: [" . $values_list . "] [" . sizeof($my_array) . "] <BR>";
// } else {
// $ats .= "Failed #$op_made #$bindcount on //params($s) values($v) // ".$param_list."// [".$values_list."] [".sizeof($my_array)."]<BR>";
// }
// $my_operation = null;
// $this->pdo->commit();
// $this->pdo = null;
}
catch (Exception $e)
{
$ats .= 'Something wrong: ' . $e->getMessage();
}
else:
if ($op_chars > 0):
throw new Exception("Unsafe chars cannot be tolerated ");
endif;
if ($op_stop > 0):
throw new Exception("Some keys or values do not match requirements. //v $v //s $s");
endif;
if ($s <= $K_LEAST):
throw new Exception("Structure keys count failed (least). //v $v //s $s");
endif;
if ($s >= $K_MOST):
throw new Exception("Structure keys count failed (most). //v $v //s $s");
endif;
if ($v > 0 && preg_match("($secure_purposes)", $param_list) && array_key_exists($KEY_V, $splited_array) && array_key_exists($KEY_S, $splited_array)):
throw new Exception("Missing some structure. //v $v //s $s #$op_stop");
endif;
endif;
}
else:
throw new Exception("Limited operations.");
endif;
}
catch (Exception $e)
{
$ats .= 'Something wrong: ' . $e->getMessage();
}
return $ats;
else:
return false;
endif;
}
public function handle_query($my_array, $safe_code)
{
$private_safe_code = $this->safe_code;
try
{
if ($private_safe_code == $safe_code): // some user security check
$get_private_func = $this->build_query($my_array);
return $get_private_func;
else:
throw new Exception("Something wrong.");
endif;
}
catch (Exception $e)
{
return $e->getMessage();
}
}
}
Затем вызовите функцию и выполните запрос.
//Insert into
$operation_examples[] = [
"STRUCTURE" => [ // structure create only by programmer this part safe from inputs
'INSERT INTO' => "basic_table (indx, values1, values2)",
'VALUES' => " (:val1, :val2, :val3)"
],
"SETPARAM" => [// binding parameters (PDO prepared st.)
":val1" => "somevalue",
":val2" => "somevalue"
],
"FMODE" => ["execute"] // switch to fetch if needed
];
//DELETE FROM 2 TIMES WITH INDEX
foreach (range(1, 2) as $i) {
$operation_examples[] = [
"STRUCTURE" => [ // structure create only by programmer this part safe from inputs
'DELETE FROM' => "basic_table",
'WHERE' => "my_index = :myindex"
],
"SETPARAM" => [// binding parameters (PDO prepared st.)
":myindex" => "$i"
],
"FMODE" => ["execute"] // switch to fetch if needed
];
}
$access = new my_op($pdo);
$doing_operation = $access->handle_query($operation_examples, "@@@");
print $doing_operation; // get created result with provided information.
Выход:
execute Success #1 # on //params(2) values(2) // PARAMLIST: INSERT INTO basic_table (indx, values1, values2)VALUES (:val1, :val2, :val3)//
VALUESLIST: [:val1 somevalue :val2 somevalue ] [3]
execute Success #2 # on //params(2) values(1) // PARAMLIST: DELETE FROM basic_tableWHERE my_index = :myindex//
VALUESLIST: [:myindex 1 ] [3]
execute Success #3 # on //params(2) values(1) // PARAMLIST: DELETE FROM basic_tableWHERE my_index = :myindex//
VALUESLIST: [:myindex 2 ] [3]
```
1 ответ
Вы превратили простые подготовленные SQL-операторы в это чудовище, которое чрезвычайно трудно читать и которое открыто для SQL-инъекций. Убери это! Не идите по этому пути. Это совершенно не нужно.
Посмотрите, насколько простыми могут быть подготовленные операторы.
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli('localhost', 'user', 'password', 'test');
$mysqli->set_charset('utf8mb4'); // always set the charset
$index = 1;
$stmt = $mysqli->prepare('UPDATE my_orders SET status = "waiting" WHERE myindex = ?');
$stmt->bind_param('s', $index);
$stmt->execute();
Вам не нужен этот сложный класс. Все, что вам нужно, это подготовленные отчеты. Если вам нужно что-то более простое с большей функциональностью, вы можете использовать PDO. Я настоятельно рекомендую по возможности использовать PDO.
Тот же код с использованием подготовленных операторов:
<?php
$pdo = new PDO("mysql:host=localhost;dbname=test;charset=utf8mb4", 'user', 'password', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false
]);
$index = 1;
$stmt = $mysqli->prepare('UPDATE my_orders SET status = "waiting" WHERE myindex = ?');
$stmt->execute([$index]);
На несвязанном примечании, пожалуйста, используйте подходящую среду IDE с инструментом статического анализа, подсветкой и форматированием синтаксиса. Этот код в настоящее время не читается.
Что, если я изменю mysqli_query на подготовленные операторы pdo, но оставлю ту же функцию only_update, просто изменив ее синтаксисом и параметрами pdo?
— Анархия
В чем смысл? MySQLi также подготовил операторы. Не создавайте функций, которые усложнят программирование. PDO проще по сравнению с MySQLi, но если вы сохраните текущий код, вы не увидите больших улучшений.
— Дхарман
Я хочу делать операции только с функциями и массивами. Я редактирую свой код и изменяю его с помощью pdo, проверяю его и говорю, что вы думаете.
— Анархия
@ZujisKarolis Это хуже, чем раньше. Вы по-прежнему не связываете никакие параметры. Имена переменных ничего не значат. Код все еще не отформатирован. У тебя очень странно
filter_var_array($my_values,FILTER_SANITIZE_STRING,FILTER_FLAG_STRIP_HIGH);
.— Дхарман
@ZujisKarolis Просто удали весь этот класс. Поверьте, вы действительно не хотите так поступать. Придерживайтесь старого доброго SQL. Что касается привязки параметров, я понятия не имею, что вы делаете, но она все еще уязвима для SQL-инъекций. Даже если вам удастся правильно их связать, весь класс все равно будет уязвим для SQL-инъекции. Не делай этого.
— Дхарман
Показать 5 больше комментариев