Quoting and Placeholder Support

Một phần của tài liệu thư viện số dau mysql cookbook (Trang 158 - 161)

In Recipe 2.9, we developed a PHP sql_quote( ) function for PHP to handle quoting, escaping, and NULL (unset) values, so that any value can be inserted easily into a query:

function sql_quote ($str) {

if (!isset ($str)) return ("NULL");

$func = function_exists ("mysql_escape_string") ? "mysql_escape_string"

: "addslashes";

return ("'" . $func ($str) . "'");

}

If we add sql_quote( ) to the MySQL_Access class, it becomes available automatically to any class instance as an object method and you can construct query strings that include properly quoted values like so:

$stmt = sprintf ("INSERT INTO profile (name,birth,color,foods,cats) VALUES(%s,%s,%s,%s,%s)",

$conn->sql_quote ("De'Mont"), $conn->sql_quote ("1973-01-12"), $conn->sql_quote (NULL),

$conn->sql_quote ("eggroll"), $conn->sql_quote (4));

$conn->issue_query ($stmt);

In fact, we can employ the sql_quote( ) method as the basis for a placeholder emulation mechanism, to be used as follows:

1. Begin by passing a query string to the prepare_query( ) method.

2. Indicate placeholders in the query string by using ? characters.

3. Execute the query and supply an array of values to be bound to the query, one value per placeholder. (To bind NULL to a placeholder, pass the PHP NULL value.)

One way to perform parameter binding is to do a lot of pattern matching and substitution in the query string wherever ? occurs as a placeholder character. An easier approach is simply to break the query string at the ? characters, then glue the pieces back together at query

execution time with the properly quoted data values inserted between the pieces. Splitting the query also is an easy way to find out how many placeholders there are (it's the number of pieces, minus one). That's useful for determining whether or not the proper number of data values is present when it comes time to bind those values to the placeholders.

The prepare_query( ) method is quite simple. All it does is split up the query string at ? characters, placing the result into the $query_pieces array for later use at parameter- binding time:

function prepare_query ($query) {

$this->query_pieces = explode ("?", $query);

return (TRUE);

}

We could invent new calls for binding data values to the query and for executing it, but it's also possible to modify issue_query( ) a little, to have it determine what to do by

examining the type of its argument. If the argument is a string, it's interpreted as a query that should be executed directly (which is how issue_query( ) behaved before). If the

argument is an array, it is assumed to contain data values to be bound to a previously prepared statement. With this change, issue_query( ) looks like this:

function issue_query ($arg = "") {

if ($arg == "") # if no argument, assume prepared statement $arg = array ( ); # with no values to be bound

if (!$this->connect ( )) # establish connection to server if return (FALSE); # necessary

if (is_string ($arg)) # $arg is a simple query $query_str = $arg;

else if (is_array ($arg)) # $arg contains data values for placeholders

{

if (count ($arg) != count ($this->query_pieces) - 1) {

$this->errno = -1;

$this->errstr = "data value/placeholder count mismatch";

$this->error ("Cannot execute query");

return (FALSE);

}

# insert data values into query at placeholder # positions, quoting values as we go

$query_str = $this->query_pieces[0];

for ($i = 0; $i < count ($arg); $i++) {

$query_str .= $this->sql_quote ($arg[$i]) . $this->query_pieces[$i+1];

} }

else # $arg is garbage {

$this->errno = -1;

$this->errstr = "unknown argument type to issue_query";

$this->error ("Cannot execute query");

return (FALSE);

}

$this->num_rows = 0;

$this->result_id = mysql_query ($query_str, $this->conn_id);

$this->errno = mysql_errno ( );

$this->errstr = mysql_error ( );

if ($this->errno) {

$this->error ("Cannot execute query: $query_str");

return (FALSE);

}

# get number of affected rows for non-SELECT; this also returns # number of rows for a SELECT

$this->num_rows = mysql_affected_rows ($this->conn_id);

return ($this->result_id);

}

Now that quoting and placeholder support is in place, the class provides three ways of issuing queries. First, you can write out the entire query string literally and perform quoting, escaping, and NULL handling yourself:

$conn->issue_query ("INSERT INTO profile (name,birth,color,foods,cats) VALUES('De\'Mont','1973-01-12',NULL,'eggroll','4')");

Second, you can use the sql_quote( ) method to insert data values into the query string:

$stmt = sprintf ("INSERT INTO profile (name,birth,color,foods,cats) VALUES(%s,%s,%s,%s,%s)",

$conn->sql_quote ("De'Mont"), $conn->sql_quote ("1973-01-12"), $conn->sql_quote (NULL),

$conn->sql_quote ("eggroll"), $conn->sql_quote (4));

$conn->issue_query ($stmt);

Third, you can use placeholders and let the class interface handle all the work of binding values to the query:

$conn->prepare_query ("INSERT INTO profile (name,birth,color,foods,cats) VALUES(?,?,?,?,?)");

$conn->issue_query (array ("De'Mont", "1973-01-12", NULL, "eggroll", 4));

The MySQL_Access and Cookbook_DB_Access classes now provide a reasonably convenient means of writing PHP scripts that is easier to use than the native MySQL PHP calls. The class interface also includes placeholder support, something that PHP does not provide at all.

The development of these classes illustrates how you can write your own interface that hides MySQL-specific details. The interface is not without its shortcomings, naturally. For example, it

allows you to prepare only one statement at a time, unlike DBI and JDBC, which support multiple simultaneous prepared statements. Should you require such functionality, you might consider how to reimplement MySQL_Access to provide it.

Một phần của tài liệu thư viện số dau mysql cookbook (Trang 158 - 161)

Tải bản đầy đủ (PDF)

(1.068 trang)