Improving this longwinded and repetative sql script
I've been learning about DRY code and my code isn't DRY...
For example, I have a custom CMS and I save basically a name, content and a publish status for a few things... like an article, a user, a event. To submit a form, I submit to a file (process.php) which has a switch in it like so:
switch($_POST['process']) {
case 'speaker':
if($_POST['speaker_id']) {
$sql = '
UPDATE speakers
SET speaker_name="' . mysql_escape_string($_POST['speaker_name']) . '",
speaker_content="' . mysql_escape_string($_POST['speaker_content']) . '",
speaker_status="' . $_POST['speaker_status'] . '"
WHERE speaker_id="' . $_POST['speaker_id'] . '"
LIMIT 1
';
} else {
$sql = '
INSERT INTO speakers
SET speaker_name="' . mysql_escape_string($_POST['speaker_name']) . '",
speaker_content="' . mysql_escape_string($_POST['speaker_content']) . '",
speaker_status="' . $_POST['speaker_status'] . '"
';
}
mysql_query($sql);
if($_POST['speaker_id']) {
header('Location: speakers?speaker_id=' . $_POST['speaker_id']);
} else {
header('Location: speakers?speaker_id=' . mysql_insert_id);
}
break;
case 'event':
if($_POST['event_id']) {
$sql = '
UPDATE events
SET event_name="' . mysql_escape_string($_POST['event_name']) . '",
event_content="' . mysql_escape_string($_POST['event_content']) . '",
event_status="' . $_POST['event_status'] . '"
WHERE event_id="' . $_POST['event_id'] . '"
LIMIT 1
';
} else {
$sql = '
INSERT INTO events
SET event_name="' . mysql_escape_string($_POST['event_name']) . '",
event_content="' . mysql_escape_string($_POST['event_content']) . '",开发者_StackOverflow
event_status="' . $_POST['event_status'] . '"
';
}
mysql_query($sql);
if($_POST['event_id']) {
header('Location: events?event_id=' . $_POST['event_id']);
} else {
header('Location: events?event_id=' . mysql_insert_id);
}
break;
case 'article':
if($_POST['article_id']) {
$sql = '
UPDATE articles
SET article_name="' . mysql_escape_string($_POST['article_name']) . '",
article_content="' . mysql_escape_string($_POST['article_content']) . '",
article_status="' . $_POST['article_status'] . '",
article_modified="' . $_POST['article_modified'] . '"
WHERE article_id="' . $_POST['article_id'] . '"
LIMIT 1
';
} else {
$sql = '
INSERT INTO articles
SET article_name="' . mysql_escape_string($_POST['article_name']) . '",
article_content="' . mysql_escape_string($_POST['article_content']) . '",
article_status="' . $_POST['article_status'] . '"
';
}
mysql_query($sql);
if($_POST['article_id']) {
header('Location: articles?article_id=' . $_POST['article_id']);
} else {
header('Location: articles?article_id=' . mysql_insert_id);
}
break;
}
Despite some basic variations, like different table names and column names, and perhaps there sometimes being a couple more or less columns to populate, the code is literally the same and programming like this feels more like data entry than creativity.
I imagine there's a way to create a class for this so that all the below code could be achieved in 1/3 the amount. Is there some sort of streamlined mysql insert / update method/strategy?
In my head, I'm thinking if I name all my inputs the same as they are in the table, ie if the column is called 'speaker_name' and the input is..
<input type="text" name="speaker_name" />
...I wonder if I could have a function which went through the $_POST array and simply updated the appropriate fields. Is this sound logic?
Perhaps I would have a hidden input in the form which was the 'table' variable which let the function know which table to update and it takes care of the rest.
Excuse me while I just thought out-loud. Any ideas would be really cool!
My newbie solution Here's what I have i got working
if($_POST['id']) {
$sql = 'UPDATE ';
} else {
$sql = 'INSERT INTO ';
}
// number of rows in array
$total = count($_POST);
// number of commas = total of values minus 1
$commas = $total - 1;
// starting number
$count = 1;
foreach ($_POST as $key => $value) {
if($count == 1)
{
$sql .= mysql_real_escape_string($value) . ' SET ';
}
else
{
if( $count < $total )
{
$sql .= $key . '="' . mysql_real_escape_string($value) . '"';
if($count != $commas)
{
$sql .= ', ';
}
}
elseif( $_POST['id'] )
{
$sql .= ' WHERE ' . $key . '="' . mysql_real_escape_string($value) . '"';
}
}
$count = $count + 1;
}
mysql_query($sql);
if($_POST['id']) {
header('Location: ' . $_POST['process'] . '?id=' . $_POST['id'] . '');
} else {
header('Location: ' . $_POST['process'] . '?id=' . mysql_insert_id());
}
To do this means my form designs need to have a pretty strict setup ie the first hidden input holds the table name, the last input is the id number of the row in the table being edited (if it exists).
I know its far from good... but a lot better than the hundreds of lines it previously took.
1) some flaws in your concept
every piece of data you're going to put into quotes in your query, should be processed with mysql_real_escape_string, as you cannot know what can be inside.
never use a table name passed from the client side. there can be malicious code instead of mere table name as well.
same for the field names. every identifier should be hardcoded in your script.
2) as for the DRY - it's simple. just note similar parts in your code and put them into function. only fields differ? okay, make a function that take fields list and produce an SQL statement of them.
Luckily, Mysql let us use similar syntax for both insert and update. So, a very simple function like this one can help:
function dbSet($fields) {
$set='';
foreach ($fields as $field) {
if (isset($_POST[$field])) {
$set.="`$field`='".mysql_real_escape_string($_POST[$field])."', ";
}
}
return substr($set, 0, -2);
}
So, you can make your code shorter:
case 'speaker':
$table = "speakers";
$fields = explode(" ","speaker_name speaker_content speaker_status");
if(isset($_POST['speaker_id'])) {
$id = intval($_POST['speaker_id']);
$query = "UPDATE $table SET ".dbSet($fields)." WHERE id=$id";
} else {
$query = "INSERT INTO $table SET ".dbSet($fields);
}
mysql_query($sql) or trigger_error(mysql_error().$query);
if($_POST['speaker_id']) $id = mysql_insert_id();
header('Location: speakers?speaker_id='.$id);
break;
if all your actions are such alike, you can make more high leveled functions:
case 'speaker':
$table = "speakers";
$fields = explode(" ","speaker_name speaker_content speaker_status");
if(isset($_POST['speaker_id'])) {
$id = intval($_POST['speaker_id']);
dbUpdate($table,$fields,$id);
} else {
$id = dbInsert($table,$fields);
}
header('Location: speakers?speaker_id='.$id);
exit;
break;
and even more high level
case 'speaker':
$table = "speakers";
$fields = explode(" ","speaker_name speaker_content speaker_status");
$id = dbMagic();
header('Location: speakers?speaker_id='.$id);
exit;
break;
But I won't go into that. I'd stop at 1st option, because it's pretty straightforward and there are always some little things not fit into such a broad concept.
While your alignment of column names and perhaps a hidden form field for table name would be quicker to code and more elegant to code against you would be opening yourself up to someone spoofing an HTML file to control the inserts to a table! Consider refactoring in other ways to optimize your library. Keep thinking / you're bound to come up with a better solution within the context of your problem domain.
Usually, one has some base models to do this, for example (don't be to harsh about it's failtings, I just want to give a short example):
class Speaker {
function __construct($data){
foreach($data as $key => $value) $this->$key = $value;
}
function save(){
$query =
($this->speaker_id ? 'UPDATE ':'INSERT INTO').' speakers '
."SET speaker_name='%s',
speaker_content='%s',
speaker_status='%s'"
.($this->speaker_id ? ' WHERE speaker_id=%d ':'');
mysql_query(sprintf($query(
mysql_real_escape_string($this->speaker_name),
mysql_real_escape_string($this->speaker_content),
mysql_real_escape_string($this->speaker_status),
intval($this->speaker_id)));
if(!$this->speaker_id) $this->speaker_id = mysql_insert_id();
}
}
And then:
switch($_POST['process']) {
case 'speaker':
$speaker = new Speaker($_POST);
$speaker->save();
header('Location: /speakers?speaker_id='.$speaker->speaker_id);
But for new projects, I would suggest a more complete MVC pattern (where Event & Speaker can be subclasses from some base databasemodel, possibly able to generate some form based on settings), and use prepared statements, they will make your sql queries easier & safer. never use addslashes, for good ol' mysql_query use mysql_real_escape_string
精彩评论