[php] Why shouldn't I use mysql_* functions in PHP?

Ease of use

The analytic and synthetic reasons were already mentioned. For newcomers there's a more significant incentive to stop using the dated mysql_ functions.

Contemporary database APIs are just easier to use.

It's mostly the bound parameters which can simplify code. And with excellent tutorials (as seen above) the transition to PDO isn't overly arduous.

Rewriting a larger code base at once however takes time. Raison d'ĂȘtre for this intermediate alternative:

Equivalent pdo_* functions in place of mysql_*

Using <pdo_mysql.php> you can switch from the old mysql_ functions with minimal effort. It adds pdo_ function wrappers which replace their mysql_ counterparts.

  1. Simply include_once("pdo_mysql.php"); in each invocation script that has to interact with the database.

  2. Remove the mysql_ function prefix everywhere and replace it with pdo_.

    • mysql_connect() becomes pdo_connect()
    • mysql_query() becomes pdo_query()
    • mysql_num_rows() becomes pdo_num_rows()
    • mysql_insert_id() becomes pdo_insert_id()
    • mysql_fetch_array() becomes pdo_fetch_array()
    • mysql_fetch_assoc() becomes pdo_fetch_assoc()
    • mysql_real_escape_string() becomes pdo_real_escape_string()
    • and so on...

  3. Your code will work alike and still mostly look the same:

    include_once("pdo_mysql.php"); 
    
    pdo_connect("localhost", "usrABC", "pw1234567");
    pdo_select_db("test");
    
    $result = pdo_query("SELECT title, html FROM pages");  
    
    while ($row = pdo_fetch_assoc($result)) {
        print "$row[title] - $row[html]";
    }
    

Et voilĂ .
Your code is using PDO.
Now it's time to actually utilize it.

Bound parameters can be easy to use

You just need a less unwieldy API.

pdo_query() adds very facile support for bound parameters. Converting old code is straightforward:

Move your variables out of the SQL string.

  • Add them as comma delimited function parameters to pdo_query().
  • Place question marks ? as placeholders where the variables were before.
  • Get rid of ' single quotes that previously enclosed string values/variables.

The advantage becomes more obvious for lengthier code.

Often string variables aren't just interpolated into SQL, but concatenated with escaping calls in between.

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title='" . pdo_real_escape_string($title) . "' OR id='".
   pdo_real_escape_string($title) . "' AND user <> '" .
   pdo_real_escape_string($root) . "' ORDER BY date")

With ? placeholders applied you don't have to bother with that:

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)

Remember that pdo_* still allows either or.
Just don't escape a variable and bind it in the same query.

  • The placeholder feature is provided by the real PDO behind it.
  • Thus also allowed :named placeholder lists later.

More importantly you can pass $_REQUEST[] variables safely behind any query. When submitted <form> fields match the database structure exactly it's even shorter:

pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);

So much simplicity. But let's get back to some more rewriting advises and technical reasons on why you may want to get rid of mysql_ and escaping.

Fix or remove any oldschool sanitize() function

Once you have converted all mysql_ calls to pdo_query with bound params, remove all redundant pdo_real_escape_string calls.

In particular you should fix any sanitize or clean or filterThis or clean_data functions as advertised by dated tutorials in one form or the other:

function sanitize($str) {
   return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}

Most glaring bug here is the lack of documentation. More significantly the order of filtering was in exactly the wrong order.

  • Correct order would have been: deprecatedly stripslashes as the innermost call, then trim, afterwards strip_tags, htmlentities for output context, and only lastly the _escape_string as its application should directly preceed the SQL intersparsing.

  • But as first step just get rid of the _real_escape_string call.

  • You may have to keep the rest of your sanitize() function for now if your database and application flow expect HTML-context-safe strings. Add a comment that it applies only HTML escaping henceforth.

  • String/value handling is delegated to PDO and its parameterized statements.

  • If there was any mention of stripslashes() in your sanitize function, it may indicate a higher level oversight.

    • That was commonly there to undo damage (double escaping) from the deprecated magic_quotes. Which however is best fixed centrally, not string by string.

    • Use one of the userland reversal approaches. Then remove the stripslashes() in the sanitize function.

    Historic note on magic_quotes. That feature is rightly deprecated. It's often incorrectly portrayed as failed security feature however. But magic_quotes are as much a failed security feature as tennis balls have failed as nutrition source. That simply wasn't their purpose.

    The original implementation in PHP2/FI introduced it explicitly with just "quotes will be automatically escaped making it easier to pass form data directly to msql queries". Notably it was accidentially safe to use with mSQL, as that supported ASCII only.
    Then PHP3/Zend reintroduced magic_quotes for MySQL and misdocumented it. But originally it was just a convenience feature, not intend for security.

How prepared statements differ

When you scramble string variables into the SQL queries, it doesn't just get more intricate for you to follow. It's also extraneous effort for MySQL to segregate code and data again.

SQL injections simply are when data bleeds into code context. A database server can't later spot where PHP originally glued variables inbetween query clauses.

With bound parameters you separate SQL code and SQL-context values in your PHP code. But it doesn't get shuffled up again behind the scenes (except with PDO::EMULATE_PREPARES). Your database receives the unvaried SQL commands and 1:1 variable values.

While this answer stresses that you should care about the readability advantages of dropping mysql_. There's occasionally also a performance advantage (repeated INSERTs with just differing values) due to this visible and technical data/code separation.

Beware that parameter binding still isn't a magic one-stop solution against all SQL injections. It handles the most common use for data/values. But can't whitelist column name / table identifiers, help with dynamic clause construction, or just plain array value lists.

Hybrid PDO use

These pdo_* wrapper functions make a coding-friendly stop-gap API. (It's pretty much what MYSQLI could have been if it wasn't for the idiosyncratic function signature shift). They also expose the real PDO at most times.
Rewriting doesn't have to stop at using the new pdo_ function names. You could one by one transition each pdo_query() into a plain $pdo->prepare()->execute() call.

It's best to start at simplifying again however. For example the common result fetching:

$result = pdo_query("SELECT * FROM tbl");
while ($row = pdo_fetch_assoc($result)) {

Can be replaced with just an foreach iteration:

foreach ($result as $row) {

Or better yet a direct and complete array retrieval:

$result->fetchAll();

You'll get more helpful warnings in most cases than PDO or mysql_ usually provide after failed queries.

Other options

So this hopefully visualized some practical reasons and a worthwile pathway to drop mysql_.

Just switching to doesn't quite cut it. pdo_query() is also just a frontend onto it.

Unless you also introduce parameter binding or can utilize something else from the nicer API, it's a pointless switch. I hope it's portrayed simple enough to not further the discouragement to newcomers. (Education usually works better than prohibition.)

While it qualifies for the simplest-thing-that-could-possibly-work category, it's also still very experimental code. I just wrote it over the weekend. There's a plethora of alternatives however. Just google for PHP database abstraction and browse a little. There always have been and will be lots of excellent libraries for such tasks.

If you want to simplify your database interaction further, mappers like Paris/Idiorm are worth a try. Just like nobody uses the bland DOM in JavaScript anymore, you don't have to babysit a raw database interface nowadays.