Replacing mysql_* functions with PDO and prepared statements

后端 未结 4 1602
抹茶落季
抹茶落季 2020-11-27 19:59

I\'ve always done the simple connection of mysql_connect, mysql_pconnect:

$db = mysql_pconnect(\'*host*\', \'*user*\', \'*pass*\');         


        
相关标签:
4条回答
  • 2020-11-27 20:15

    Yes, :something is a named placeholder in PDO, ? is an anonymous placeholder. They allow you to either bind values one by one or all at once.

    So, basically that makes four options to provide your query with values.

    One by one with bindValue()

    This binds a concrete value to your placeholder as soon as you call it. You may even bind hard coded strings like bindValue(':something', 'foo') if desired.

    Providing a parameter type is optional (but suggested). However, since the default is PDO::PARAM_STR, you only need to specify it when it is not a string. Also, PDO will take care of the length here - there is no length parameter.

    $sql = '
      SELECT *
      FROM `users`
      WHERE
        `name` LIKE :name
        AND `type` = :type
        AND `active` = :active
    ';
    $stm = $db->prepare($sql);
    
    $stm->bindValue(':name', $_GET['name']); // PDO::PARAM_STR is the default and can be omitted.
    $stm->bindValue(':type', 'admin'); // This is not possible with bindParam().
    $stm->bindValue(':active', 1, PDO::PARAM_INT);
    
    $stm->execute();
    ...
    

    I usually prefer this approach. I find it the cleanest and most flexible.

    One by one with bindParam()

    A variable is bound to your placeholder that will be read when the query is executed, NOT when bindParam() is called. That may or may not be what you want. It comes in handy when you want to repeatedly execute your query with different values.

    $sql = 'SELECT * FROM `users` WHERE `id` = :id';
    $stm = $db->prepare($sql);
    $id = 0;
    $stm->bindParam(':id', $id, PDO::PARAM_INT);
    
    $userids = array(2, 7, 8, 9, 10);
    foreach ($userids as $userid) {
      $id = $userid;
      $stm->execute();
      ...
    }
    

    You only prepare and bind once which safes CPU cycles. :)

    All at once with named placeholders

    You just drop in an array to execute(). Each key is a named placeholder in your query (see Bill Karwins answer). The order of the array is not important.

    On a side note: With this approach you cannot provide PDO with data type hints (PDO::PARAM_INT etc.). AFAIK, PDO tries to guess.

    All at once with anonymous placeholders

    You also drop in an array to execute(), but it is numerically indexed (has no string keys). The values will replace your anonymous placeholders one by one in the order they appear in your query/array - first array value replaces first placeholder and so forth. See erm410's answer.

    As with the array and named placeholders, you cannot provide data type hints.

    What they have in common

    • All of those require you to bind/provide as much values as you have placeholders. If you bind too many/few, PDO will eat your children.
    • You don't have to take care about escaping, PDO handles that. Prepared PDO statements are SQL injection safe by design. However, that's not true for exec() and query() - you should generally only use those two for hardcoded queries.

    Also be aware that PDO throws exceptions. Those could reveal potentially sensitive information to the user. You should at least put your initial PDO setup in a try/catch block!

    If you don't want it to throw Exceptions later on, you can set the error mode to warning.

    try {
      $db = new PDO(...);
      $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING)
    } catch (PDOException $e) {
      echo 'Oops, something went wrong with the database connection.';
    }
    
    0 讨论(0)
  • 2020-11-27 20:25

    To answer the length question, specifying it is optional unless the param you are binding is an OUT parameter from a stored procedure, so in most cases you can safely omit it.

    As far as safety goes, escaping is done behind the scenes when you bind the parameters. This is possible because you had to create a database connection when you created the object. You are also protected from SQL injection attacks since by preparing the statement, you are telling your database the format of the statement before user input can get anywhere near to it. An example:

    $id = '1; MALICIOUS second STATEMENT';
    
    mysql_query("SELECT * FROM `users` WHERE `id` = $id"); /* selects user with id 1 
                                                              and the executes the 
                                                              malicious second statement */
    
    $stmt = $pdo->prepare("SELECT * FROM `users` WHERE `id` = ?") /* Tells DB to expect a 
                                                                     single statement with 
                                                                     a single parameter */
    $stmt->execute(array($id)); /* selects user with id '1; MALICIOUS second 
                                   STATEMENT' i.e. returns empty set. */
    

    Thus, in terms of safety, your examples above seem fine.

    Finally, I agree that binding parameters individually is tedious and is just as effectively done with an array passed to PDOStatement->execute() (see http://www.php.net/manual/en/pdostatement.execute.php).

    0 讨论(0)
  • 2020-11-27 20:27

    Thanks for the interesting question. Here you go:

    It escapes dangerous characters,

    Your concept is utterly wrong.
    In fact "dangerous characters" is a myth, there are none. And mysql_real_escape_string escaping but merely a string delimiters. From this definition you can conclude it's limitations - it works only for strings.

    however, it is still vulnerable to other attacks which can contain safe characters but may be harmful to either displaying data or in some cases, modifying or deleting data maliciously.

    You're mixing here everything.
    Speaking of database,

    • for the strings it is NOT vulnerable. As long as your strings being quoted and escaped, they cannot "modify or delete data maliciously".*
    • for the other data typedata - yes, it's useless. But not because it is somewhat "unsafe" but just because of improper use.

    As for the displaying data, I suppose it is offtopic in the PDO related question, as PDO has nothing to do with displaying data either.

    escaping user input

    ^^^ Another delusion to be noted!

    • a user input has absolutely nothing to do with escaping. As you can learn from the former definition, you have to escape strings, not whatever "user input". So, again:

      • you have escape strings, no matter of their source
      • it is useless to escape other types of data, no matter of the source.

    Got the point?
    Now, I hope you understand the limitations of escaping as well as the "dangerous characters" misconception.

    It is to my understanding that using PDO/prepared statements is a much safer

    Not really.
    In fact, there are four different query parts which we can add to it dynamically:

    • a string
    • a number
    • an identifier
    • a syntax keyword.

    so, you can see that escaping covers only one issue. (but of course, if you treat numbers as strings (putting them in quotes), when applicable, you can make them safe as well)

    while prepared statements cover - ugh - whole 2 isues! A big deal ;-)

    For the other 2 issues see my earlier answer, In PHP when submitting strings to the database should I take care of illegal characters using htmlspecialchars() or use a regular expression?

    Now, function names are different so no longer will my mysql_query, mysql_fetch_array, mysql_num_rows etc work.

    That is another, grave delusion of PHP users, a natural disaster, a catastrophe:

    Even when utilizing old mysql driver, one should never use bare API functions in their code! One have to put them in some library function for the everyday usage! (Not as a some magic rite but just to make the code shorter, less repetitive, error-proof, more consistent and readable).

    The same goes for the PDO as well!

    Now on with your question again.

    but by using them does this eliminate the need to use something like mysql_real_escape_string?

    YES.

    But I think this is roughly the idea of what should be done to fetch a user from a database

    Not to fetch, but to add a whatever data to the query!

    you have to given a length after PDO:PARAM_STR if I'm not mistaken

    You can, but you don't have to.

    Now, is this all safe?

    In terms of database safety there are just no weak spots in this code. Nothing to secure here.

    for the displaying security - just search this site for the XSS keyword.

    Hope I shed some light on the matter.

    BTW, for the long inserts you can make some use of the function I wrote someday, Insert/update helper function using PDO

    However, I am not using prepared statements at the moment, as I prefer my home-brewed placeholders over them, utilizing a library I mentioned above. So, to counter the code posted by the riha below, it would be as short as these 2 lines:

    $sql  = 'SELECT * FROM `users` WHERE `name`=?s AND `type`=?s AND `active`=?i';
    $data = $db->getRow($sql,$_GET['name'],'admin',1);
    

    But of course you can have the same code using prepared statements as well.


    * (yes I am aware of the Schiflett's scaring tales)

    0 讨论(0)
  • 2020-11-27 20:29

    I never bother with bindParam() or param types or lengths.

    I just pass an array of parameter values to execute(), like this:

    $stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id");
    $stmt->execute( array(':user_id' => $user_id) );
    
    $stmt = $dbh->prepare("INSERT INTO `users` (username, email)
                            VALUES (:username, :email)");
    $stmt->execute( array(':username'=>$username, ':email'=>$email) );
    

    This is just as effective, and easier to code.

    You may also be interested in my presentation SQL Injection Myths and Fallacies, or my book SQL Antipatterns: Avoiding the Pitfalls of Database Programming.

    0 讨论(0)
提交回复
热议问题