问题
Ok so im new to binding, here is some code that works. I learned this format from a tutorial but i imagine there is more efficent ways to do it. In my example there is 4 names but in reality i will be doing a lot of inserts and updates in a project im working on that will have 20 or so fields. I like this approach for its clarity but obviously when your talking 20 fields or more it does take a lot of real estate. Lets look at my code first.
Here are the functions it uses:
// prepare the statement
public function query($query){
$this->stmt = $this->dbh->prepare($query);
}
public function bind($param, $value, $type = null){
if (is_null($type)) {
switch (true) {
case is_int($value):
$type = PDO::PARAM_INT;
break;
case is_bool($value):
$type = PDO::PARAM_BOOL;
break;
case is_null($value):
$type = PDO::PARAM_NULL;
break;
default:
$type = PDO::PARAM_STR;
}
}
// run the binding process
$this->stmt->bindValue($param, $value, $type);
}
// execute the prepared statement
public function execute(){
return $this->stmt->execute();
}
and now the actual code
$database->query("
INSERT INTO users(
user_name,
user_password_hash,
user_email,
user_activation_hash
)
VALUES(
:user_name,
:user_password_hash,
:user_email,
:user_activation_hash
)
");
// bind the values
$database->bind(":user_name", "$this->user_name");
$database->bind(":user_password_hash", "$this->user_password_hash");
$database->bind(":user_email", "$this->user_email");
$database->bind(":user_activation_hash", "$this->user_activation_hash");
// execute the statement and insert the values into the database
$database->execute();
it just cries out for a loop, especially since i have a habit of calling post fields, input fields, variables and placeholders the same name, not sure if that is a good or a bad thing but i find its helpful for me when dealing with large forms which i will be.
in any case i could do something like this:
$placeholder_array = array(
"user_name" => "\$this->user_name",
"user_password_hash" => "\$this->user_password_hash",
"user_email" => "\$this->user_email",
"user_activation_hash" => "\$this->user_activation_hash"
);
// well use this copy to edit the array keys and keep original for the binding
$placeholder_copy = $placeholder_array;
// turn the array into a string i.e user_name, user_password_hash....
$fields = implode (", ", array_keys($placeholder_array));
// foreach to add : placeholder prefix for binding
foreach ($placeholder_copy as $key => $value){
$placeholder_copy [':'.$key] = $value;
unset($placeholder_copy[$key]);
}
// turn the copy array which has prefix :user_name into a string
$placeholders = implode (", ", array_keys($placeholder_copy));
$database->query("
INSERT INTO users($fields)
VALUES($placeholders)
");
// bind the values
foreach ($placeholder_copy as $bind_values => $value){
echo '$database->bind("'.$bind_values.'", "'.$value.'");' . "<br />";
}
// execute the statement and insert the values into the database
$database->execute();
i could then turn this into a function with parameters for passing in the associative array and the table name to keep my main code much cleaner.
Now imagine i am going to be doing any amount of these as the project im working on involves tons of big forms submitting data to users. I'm new to PDO and trying to grasp it so there maybe a simpler way of structuring these types of queries, i had a look on google and stackflow but i didnt really get what they were doing so i thought doing my own one would allow people to explain to me better what is going on, i would rather get this right starting my project than have to go back and change everything later. So is there a better approach or is this one ok?
Really appreciate any feedback and im glad now i took peoples advice on here and made the move to PDO.
回答1:
What I've done recently and continuing to improve, is building a helper class to simplify PDO based SQL statements in my applications.
Lets take a tour with your example. You want to insert data into a user table:
INSERT INTO users(
user_name,
user_password_hash,
user_email,
user_activation_hash
) VALUES(
:user_name,
:user_password_hash,
:user_email,
:user_activation_hash
)
For inserting a single record, my SQL is constructed like this:
function myInsertSingle($PDO, $table, $ins_array) {
$SQL = "INSERT INTO `".$table."` (".dbFieldList($ins_array)
.") VALUES (".dbValuePList($ins_array).")";
...continued with preparing.
$PDO
: My PDO connection.$table
: The table I want to insert to.$ins_array
: A structured array for inserting data.
For your example, the array $ins_array
will look like this:
$ins_array = array(
"user_name" => "your_user",
"user_password_hash" => "your_pw_hash",
"user_email" => "your@mail.xyz",
"user_activation_hash" => "your_other_Hash"
);
Notice the similarity to your array!
Two functions are involved. The first one gives me a list of (escaped) fieldnames.
function dbFieldList($fields) {
$set = '';
foreach ($fields as $field => $item) {
$set .= "`".$field."`,";
}
return rtrim($set, ',');
}
Basically from the above array example, the function returns
`user_name`, `user_password_hash`, `user_email`, `user_activation_hash`
...which is the list of the fields needed in the INSERT
-statement.
The other one does something similar for the values.
function dbValuePList($fields) {
$set = '';
foreach ($fields as $field => $item) {
$set .= ":".$field.",";
}
return rtrim($set, ',');
}
You guessed it, the result looks like this:
:user_name, :user_password_hash, :user_email, :user_activation_hash
Now you have your SQL statement which you can prepare. To bind the values, if I understand your class correctly, just use a loop which looks like this:
foreach ($ins_array as $field => $item) {
$PDO->bind(':'.$field,$item);
}
Now you can execute the statement.
Summary. Basically with my approach a single INSERT statement is reduced to this:
$ins_array = array(
"user_name" => "your_user",
"user_password_hash" => "your_pw_hash",
"user_email" => "your@mail.xyz",
"user_activation_hash" => "your_other_Hash"
);
myInsertSingle($PDO, 'your_table', $ins_array);
If this looks just like another piece of overhead, keep in mind, I use these functions in other SQL statements like SELECT, UPDATE, DELETE and so on.
回答2:
No, sadly, but PDO offers no help for the matter.
Yet your own approach doesn't seem to me an efficient one either.
First of all, let me indicate that your set of functions is quite useless. I understand no such direct rewriting of API functions. PDO already doing all these things.
With raw PDO you can have even more concise code:
$stm = $pdo->prepare("INSERT INTO users VALUES(NULL,?,?,?,?)");
$data = array($this->user_name,
$this->user_password_hash,
$this->user_email,
$this->user_activation_hash
);
$stm->execute($data);
Regarding your dynamical query building, I find it too much bloated yet still insecure. Some flaws are
- there is no point in bothering with ":named" placeholders as they are supposed to be human-readable but no human supposed to read them here. Not to mention PDO will convert them back into ?s before sending to mysql
- another objection against named placeholders - while Mysql (as well as HTTP) can let you have a space in a field name all right, a placeholder with a space will just crash your query.
- there is no real benefit in this code from your approach of the equal names - you are bound to write them by hand a dozen times each, mind you.
- yet if it would be used, you have no white list to check your field list against, which could be a great security breach
- too much code
- no support for
UPDATE
query which could be extremely handy as well (if you are using mysql) - when you put this code into function, you will slip to the same bad grounds as others, tempted by the possibility of saving you whole two words!
Here is my earlier approach for the matter:
$allowed = array(
"user_name", "user_password_hash", "user_email", "user_activation_hash"
);
$sql = "INSERT INTO users SET ".pdoSet($allowed, $values);
$stmt = $dbh->prepare($sql);
$stmt->execute($values);
And here goes my current approach, the best of the best (though implemented using mysqli, not PDO), using custom placeholder for the array data:
$placeholder_array = array(
"user_name" => $this->user_name,
"user_password_hash" => $this->user_password_hash,
"user_email" => $this->user_email,
"user_activation_hash" => $this->user_activation_hash
);
$db->query("INSERT INTO users SET ?u", $placeholder_array);
or, in case of direct connection between form fields and SQL columns
$allowed = array(
"user_name", "user_password_hash", "user_email", "user_activation_hash"
);
$insert = $db->filterArray($_POST,$allowed);
$db->query("INSERT INTO users SET ?u", $insert);
This way let me use INSERT IGNORE
, INSERT DELAYED
, INSERT.. ON DUPLICATE UPDATE
, UPDATE
, UPDATE
with join, and innumerable other options, supporting full SQL language.
来源:https://stackoverflow.com/questions/18326790/a-more-efficient-way-of-binding-a-big-insert-or-update