Подготовленные операторы в классе базы данных

Проблема

Итак, я пишу свое веб-приложение, и меня осенило: «Дурр, ваши вещи широко открыты для SQL-инъекций и многого другого! Перепишите класс БД!»

В настоящее время я переписываю свой класс $db, и у меня возникают серьезные проблемы с пониманием того, как я должен реализовывать подготовленные операторы.

Ранее...

Раньше я использовал что-то вроде этого:

$db->runQuery("SELECT * FROM someTable WHERE someField = '$var1'");
while ($result = mysql_fetch_array($db->result){
    // ... ugh, tedious
}

Неизменно при выполнении операторов select я беру массив и перебираю результаты.

Я это понимаю...

  1. Меня надо сжечь на костре за использование неподготовленных операторов в MySQL.
  2. Я должен сообщить mysql, какой тип параметра имеет каждая переменная. (Или я)?

Я бы хотел...

Иметь возможность передать мой запрос и значения моей новой функции (давайте использовать select в качестве примера), которая затем вернет результат для меня для работы (в виде ассоциированного массива значений);

$query  = "SELECT * FROM someTable WHERE someField = ? AND anotherField = ?";
$params = array($var1, $var2);
$result = $db->doSelect($query, $params);
// Then do all sorts of neat stuff with $result - huzzah!

У меня проблемы с...

Понимание того, как я буду собирать всю информацию воедино.

  1. Как мне представить массив значений и объединить его с моим подготовленным оператором?
  2. С указанным размытым оператором, как мне запустить его (execute()?) И вернуть массив?

Извините, если мой вопрос несколько окольный, но я устал от попыток понять его. Если требуется дополнительная информация, сообщите мне, и я ее добавлю.


person EvilChookie    schedule 14.08.2009    source источник


Ответы (2)


Вот что я написал для набора функций Prepare/Execute. Это, конечно, часть более крупного объекта БД.

/**
*   Prepares a query to be run, storing the data in $this->preparedTokens
*   Use the following characters to indicate how the data is to be put into SQL statement
*   ? -> escaped and quoted (with single quotes) before inserting
*   ^ -> inserted as is
*   & -> implodes the array escpaping each value
*   @ -> implodes the array (no escaping)
*
*   @param      string      $sql        The SQL statement to prepare
*
*   @return     int         The key of prepare sql query to be passed to $this->Execute()
*/
public function Prepare($sql) {
    $tokens = preg_split('/((?<!\\\)[@&?^])/', $sql, -1, PREG_SPLIT_DELIM_CAPTURE);

    // loop through removing any escaped values
    foreach ($tokens as $key => $val) {
        switch ($val) {
            case '?' :
            case '&' :
            case '@' :
                break;
            default :
                $tokens[$key] = preg_replace('/\\\([@&?^])/', "\\1", $val);
                break;
        } // switch
    } // foreach

    $this->preparedTokens[] = $tokens;
    end($this->preparedTokens);
    return key($this->preparedTokens);
} // function Prepare

/**
*   Creates the SQL placing the data in the appropriate places and then runs the sql
*
*   @param      int         $preparedKey        The key of the prepared sql
*   @param      array       $data               The array of data to put into the query (the count of this array must match that of the prepared query)
*
*   @return     object      false if the $preparedKey does not exist in $this->preparedTokens
*                           false if count of needed values in sql statement does not equal the number of keys in the data array
*                           otherwise, the result of $this->Query()
*/
public function Execute($preparedKey, $data) {
    if (isset($this->preparedTokens[$preparedKey])) {
        $tokens = $this->preparedTokens[$preparedKey];
        $query = '';
        $dataKey = 0;
        $count = 0;

        // count the number of tokens we have
        $validTokens = array('?', '^', '&', '@');
        foreach ($tokens as $val) {
            if (in_array($val, $validTokens)) {
                ++$count;
            } // if
        } // foreach

        // check to ensure we have the same number of tokens as data keys
        if ($count != count($data)) {
            trigger_error('Query Error: The number of values received in execute does not equal the number of values needed for the query', E_USER_ERROR);
            return false;
        } // if

        // loop through the tokens creating the sql statement
        foreach ($tokens as $val) {
            switch ($val) {
                case '?' :
                    $query .= "'" . $this->EscapeString($data[$dataKey++]) . "'";
                    break;
                case '^' :
                    $query .= $data[$dataKey++];
                    break;
                case '&' :
                    $query .= $this->ImplodeEscape($data[$dataKey++]);
                    break;
                case '@' :
                    $query .= implode(',', $data[$dataKey++]);
                    break;
                default :
                    $query .= $val;
                    break;
            } // switch
        } // foreach

        return $this->Query($query);

    } else {
        return false;
    } // if
} // function Execute

/**
*   Runs $this->Prepare() then $this->Execute() for the sql and the data
*   Use the following characters to indicate how the data is to be put into SQL statement
*   ? -> escaped and quoted (with single quotes) before inserting
*   ^ -> inserted as is
*   & -> implodes the array escpaping each value
*   @ -> implodes the array (no escaping)
*
*   @param      string      $sql        The SQL statement to prepare
*   @param      array       $data       The array of data to put into the query (the count of this array must match that of the prepared query)
*
*   @return     object      returns value from $this->Query() if Execute was successful
*                           otherwise it'll be false
*/
public function PrepareExecute($sql, $data) {
    return $this->Execute($this->Prepare($sql), $data);
} // function PrepareExecute

$this->Query() выполняет оператор MySQL, а затем возвращает разные значения в зависимости от того, что представляет собой оператор (на основе первых 6 символов оператора, обрезанных):

  • false, если не удалось (используйте $this->GetError(), чтобы получить сообщение об ошибке)
  • в случае успеха INSERT, то идентификатор вставки
  • в случае успешного выполнения DELETE, UPDATE или REPLACE количество затронутых строк
  • в случае успеха SELECT или любого другого типа запроса объект Query

Я не уверен, что это то, что вы ищете, но это может помочь.

Забыл упомянуть об этом, но большинство идей пришло из класса Pear::DB: http://pear.php.net/package/DB

person Darryl Hein    schedule 14.08.2009
comment
Спасибо Дэррил! Чтобы понять, потребуется немного просеять, но на самом деле это имеет немного больше смысла, чем некоторые из «учебников», которые я читал в Интернете по этой теме. Огромное спасибо! - person EvilChookie; 14.08.2009

Посмотрите, сможете ли вы использовать следующее. Дайте мне знать, если вам нужна более подробная реализация

call_user_func_array(array($stmt,"bind_result"), $params);
person Anshul    schedule 18.08.2009