PHP 5.6.0 released

Подготовленные запросы и хранимые процедуры

Большинство баз данных поддерживают концепцию подготовленных запросов. Что это такое? Это можно описать, как некий вид скомпилированного шаблона SQL запроса, который будет запускаться приложением и настраиваться с помощью входных параметров. У подготовленных запросов есть два главных преимущества:

  • Запрос необходимо однажды подготовить и затем его можно запускать столько раз, сколько нужно, причем как с теми же, так и с отличающимися параметрами. Когда запрос подготовлен, СУБД анализирует его, компилирует и оптимизирует план его выполнения. В случае сложных запросов этот процесс может занимать ощутимое время и заметно замедлить работу приложения, если потребуется много раз выполнять запрос с разными параметрами. При использовании подготовленного запроса СУБД анализирует/компилирует/оптимизирует запрос любой сложности только один раз, а приложение запускает на выполнение уже подготовленный шаблон. Таким образом подготовленные запросы потребляют меньше ресурсов и работают быстрее.
  • Параметры подготовленного запроса не требуется экранировать кавычками; драйвер это делает автоматически. Если в приложении используются исключительно подготовленные запросы, разработчик может быть уверен, что никаких SQL инъекций случиться не может (однако, если другие части текста запроса записаны с неэкранированными символами, SQL инъекции все же возможны; здесь речь идет именно о параметрах).

Подготовленные запросы также полезны тем, что PDO может эмулировать их, если драйвер базы данных не имеет подобного функционала. Это значит, что приложение может пользоваться одной и той же методикой доступа к данным независимо от возможностей СУБД.

Пример #1 Повторяющиеся вставки в базу с использованием подготовленных запросов

В этом примере 2 раза выполняется INSERT запрос с разными значениями name и value, которые подставляются вместо соответствующих псевдопеременных:

<?php
$stmt 
$dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
$stmt->bindParam(':name'$name);
$stmt->bindParam(':value'$value);

// вставим одну строку
$name 'one';
$value 1;
$stmt->execute();

// теперь другую строку с другими значениями
$name 'two';
$value 2;
$stmt->execute();
?>

Пример #2 Повторяющиеся вставки в базу с использованием подготовленных запросов

В этом примере 2 раза выполняется INSERT запрос с разными значениями name и valueкоторые подставляются вместо псевдопеременных ?.

<?php
$stmt 
$dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (?, ?)");
$stmt->bindParam(1$name);
$stmt->bindParam(2$value);

// вставим одну строку
$name 'one';
$value 1;
$stmt->execute();

// теперь другую строку с другими значениями
$name 'two';
$value 2;
$stmt->execute();
?>

Пример #3 Выборка данных с использованием подготовленных запросов

В этом примере производится выборка из базы по ключу, который вводит пользователь через форму. Пользовательский ввод автоматически заключается в кавычки, поэтому нет риска SQL иньекции.

<?php
$stmt 
$dbh->prepare("SELECT * FROM REGISTRY where name = ?");
if (
$stmt->execute(array($_GET['name']))) {
  while (
$row $stmt->fetch()) {
    
print_r($row);
  }
}
?>

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

Пример #4 Вызов хранимой процедуры с выходными параметрами

<?php
$stmt 
$dbh->prepare("CALL sp_returns_string(?)");
$stmt->bindParam(1$return_valuePDO::PARAM_STR4000); 

// вызов хранимой процедуры
$stmt->execute();

print 
"процедура вернула $return_value\n";
?>

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

Пример #5 Вызов хранимой процедуры с входным/выходным параметром

<?php
$stmt 
$dbh->prepare("CALL sp_takes_string_returns_string(?)");
$value 'привет';
$stmt->bindParam(1$valuePDO::PARAM_STR|PDO::PARAM_INPUT_OUTPUT4000); 

// вызов хранимой процедуры
$stmt->execute();

print 
"процедура вернула $value\n";
?>

Пример #6 Неправильное использование псевдопеременной

<?php
$stmt 
$dbh->prepare("SELECT * FROM REGISTRY where name LIKE '%?%'");
$stmt->execute(array($_GET['name']));

// псевдопеременная может использоваться только в виде отдельного значения
$stmt $dbh->prepare("SELECT * FROM REGISTRY where name LIKE ?");
$stmt->execute(array("%$_GET[name]%"));
?>

add a note add a note

User Contributed Notes 5 notes

up
31
adam at pyramidpower dot com dot au
4 years ago
Note that when using name parameters with bindParam, the name itself, cannot contain a dash '-'.

example:
<?php
$stmt
= $dbh->prepare ("INSERT INTO user (firstname, surname) VALUES (:f-name, :s-name)");
$stmt -> bindParam(':f-name', 'John');
$stmt -> bindParam(':s-name', 'Smith');
$stmt -> execute();
?>

The dashes in 'f-name' and 's-name' should be replaced with an underscore or no dash at all.

See http://bugs.php.net/43130

Adam
up
-5
oldmoscow dot mail dot ru
3 years ago
Note for MySQL: to use input/output parameters for stored procedures with PDO use PDO.Query() statement.
For example:

<?php
$dbh
->query("CAST SomeStoredProcedure($someInParameter1, $someInParameter2, @someOutParameter)");
$dbh->query("SELECT @someOutParameter");
?>

Or, if you want very much to use PDO.Prepare(), insert "SELECT @someOutParameter" in your stored procedure and then use:

<?php
$stmt
= $dbh->prepare("CAST SomeStoredProcedure(?, ?)");
$stmt ->execute(array($someInParameter1, $someInParameter2));
?>
up
-12
Sebastien Gourmand
1 year ago
Replace the query() by a prepare()/execute() for having one line like a sprintf()

old query() renamed for remember to secure.

<?php
class MyPDO extends PDO{

    const
DB_HOST='localhost';
    const
DB_PORT='3306';
    const
DB_NAME='test';
    const
DB_USER='root';
    const
DB_PASS='';

    public function
__construct($options=null){
       
parent::__construct('mysql:host='.MyPDO::DB_HOST.';port='.MyPDO::DB_PORT.';dbname='.MyPDO::DB_NAME,
                           
MyPDO::DB_USER,
                           
MyPDO::DB_PASS,$options);
    }

    public function
query($query){ //secured query with prepare and execute
       
$args = func_get_args();
       
array_shift($args); //first element is not an argument but the query itself, should removed

       
$reponse = parent::prepare($query);
       
$reponse->execute($args);
        return
$reponse;

    }

    public function
insecureQuery($query){ //you can use the old query at your risk ;) and should use secure quote() function with it
       
return parent::query($query);
    }

}

$db = new MyPDO();
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);

$t1 = isset($_GET["t1"])?$_GET["t1"]:1; // need to be securised for injonction
$t2 = isset($_GET["t2"])?$_GET["t2"]:2; // need to be securised for injonction
$t3 = isset($_GET["t3"])?$_GET["t3"]:3; // need to be securised for injonction

$ret = $db->query("SELECT * FROM table_test WHERE t1=? AND t2=? AND t3=?",$t1,$t2,$t3);
//$ret = $db->insecureQuery("SELECT * FROM table_test WHERE t1=".$db->quote($t1));

while ($o = $ret->fetch())
{
    echo
$o->nom.PHP_EOL;
}
?>
up
-19
Anonymous
6 months ago
/** Добавить модификации документа */
    public function insert_doc_modification($doc_id , array $notice ,array $modification_fields)
    {
     
        $fields = array_keys($modification_fields);
        $sql = "INSERT INTO doc_modification
                    (doc_id , " . implode( ",", $fields ) . ")
                VALUES
                    (:doc_id , :" . implode( ", :", $fields ) . " )
                ON DUPLICATE KEY UPDATE
                    `hash` = :hash ; ";
       
        $stmt = $this->pdo->prepare($sql);  
        $stmt->bindParam(':doc_id',$doc_id);
        //Создать бинды по полям
        foreach($modification_fields as $name => $val)
        {
            $stmt->bindParam(':'.$name,  $modification_fields[$name]);
        }
        //Обойти все модификации по документу
        foreach($notice as $value)
        {
            foreach($modification_fields as $name => $val)
            {
                if(empty($value[$name])) continue ;
                $modification_fields[$name] = $value[$name];
            }
            $stmt->execute();
        }

    }
up
-28
Anonymous
3 years ago
Note for MySQL: to use input/output parameters for stored procedures with PDO use PDO.Query() statement.
For example:

<?php
$dbh
->query("CAST SomeStoredProcedure($someInParameter1, $someInParameter2, @someOutParameter)");
$dbh->query("SELECT @someOutParameter");
?>

Or, if you want very much to use PDO.Prepare(), insert "SELECT @someOutParameter" in your stored procedure and then use:

<?php
$stmt
= $dbh->prepare("CAST SomeStoredProcedure(?, ?)");
$stmt ->execute(array($someInParameter1, $someInParameter2));
?>
To Top