PDA

View Full Version : How to do Mail Merge with php and MySQL



kuau
12-17-2009, 03:29 AM
I have googled this to death already and cannot find any answers, so I am hoping the DD geniuses can save the day.

What is the correct way without using global variables (eregi_replace has been deprecated) to get data from a MySQL database into variables in an html email? There are no $_POST or $_GET variables. I tried using echo with php but it would appear php does not work in an html email.

I had been using the following code (which I never really understood & may have been awful in the first place) and would really like to learn the best possible way. I don't like having Register Globals = On in the php.ini but everything goes blank if I turn it off. Thanks!


@$fp = fopen($html_email_template_file, "r");
while(!feof($fp)) {
$buffer = fgets($fp,100);
$body.= $buffer;
}
fclose ($fp);

$body = replace_email_template_variables($body);

$Subject = "Your Quote Request";
$mail = new phpmailer();
$mail->From = "info@domain.com";
$mail->FromName = "Company Name";
$mail->AddAddress($_POST['Email'], $name);
$mail->AddReplyTo('info@domain.com', 'Company Name');
$mail->IsHTML(true);
$mail->Subject = $Subject;
$mail->Body = $body;
$mail->Send();

bluewalrus
12-17-2009, 03:52 AM
I don't see any sql in there or where variables would be displayed as the name rather than value.

kuau
12-17-2009, 04:02 AM
That's because I didn't think it necessary to include the html template or the SELECT command. There a bunch of different emails but they all have variables in them that look like this:


<tr><td width="114">First Name:</td><td>%First_Name%</td></tr>
<tr><td width="114">Last Name:</td><td>%Last_Name%</td></tr>
<tr><td width="114">Age Group:</td><td>%Age_Group%</td></tr>

and there is a SQL command like:


SELECT * from table WHERE id = $id or whatever. The particular application here is people requesting a price quote on a car rental for specific dates and times, a particular car class, age group, pickup & drop off location, etc so there are a lot of variables that have to be inserted in the email body. I know how to do it in a text email with GET & POST variables, but how do you do it for an html email in which the template exists as a separate html file, and is not just a text email assembled on the fly? I would have thought this a fairly common procedure.

Almost every website has a Contact Form. Say you wanted to send something fancier than a plain text email as a response... it is the same principle using GET & POST. I would like to send a more professional looking email. I suspect there is a simple solution but I just can't find how to do it. Anyone?

djr33
12-17-2009, 07:01 AM
I'm also very confused about what you need, exactly. Are you looking to replace something in a string? Just get the contents of the file and do a search and replace operation. str_replace() will do that, if you want to replace "%name%" with $name.

Regardless, here's an explanation of the code in the first post so you know what it does:
@$fp = fopen($html_email_template_file, "r"); //open a file for use
while(!feof($fp)) { //until you get to the end of the file (file-end-of-file)
$buffer = fgets($fp,100); //get a line, only 100 characters long
$body.= $buffer; //add this line to the body of the email
} //end of the loop
fclose ($fp); //we don't need the file any more, close it

$body = replace_email_template_variables($body); //is this your function?
//I suppose it modifies the $body variable to replace values
//see my note above for doing this; str_replace() is easiest, probably

$Subject = "Your Quote Request"; //set the subject
$mail = new phpmailer(); //phpmailer() is a class, so let's make a new "mail"
$mail->From = "info@domain.com"; //add a value to it based on the class
$mail->FromName = "Company Name"; //repeat
$mail->AddAddress($_POST['Email'], $name); //...
$mail->AddReplyTo('info@domain.com', 'Company Name');
$mail->IsHTML(true);
$mail->Subject = $Subject;
$mail->Body = $body;
$mail->Send(); //finally, now it's ready, so send it

One note: the function file(), which I think is actually oddly designed, does what the first lines of the code do with more code in them. file() opens and stores the text of a file into an array separating the lines. So $f = file('--'); would be the same as the code above, then a foreach() loop would easily replace the rest there, if you for some reason want to go line by line. I suppose perhaps the idea is to truncate so lines are at most 100 characters. file_get_contents() would just get the entire text of the file without doing anything about line breaks, but you probably do want to convert a bit for the email.

kuau
12-17-2009, 04:13 PM
Dear Daniel:

Thanks so much for explaining the code. I'm always afraid to try to replace code when I don't fully get what it is doing. But now I have no choice because, with the next php upgrade on the server, this code will no longer work. Here is the function:


function replace_email_template_variables($body_text) { // This function replaces the variables in the Body text for the email
global $Today;
global $First_Name;
global $Last_Name;
global $Phone;
global $Email;
global $Age_Group;
global $Flight;
global $Cruise;
global $Credit_Card;
global $Best_Price_Net;
global $Best_Price_Full;
global $New_Pick_Date;
global $N_Pick_Time;
global $Pick_Location;
global $Pick_AP;
global $New_Drop_Date;
global $N_Drop_Time;
global $Drop_Location;
global $Drop_AP;
global $Car_Class;
global $Days_Num;
global $country;
global $employee;
global $Car_Logo;
global $agencyname;
global $Comment;
$Comment = "";

$body_text = eregi_replace('%Today%', $Today, $body_text);
$body_text = eregi_replace('%First_Name%', $First_Name, $body_text);
$body_text = eregi_replace('%Last_Name%', $Last_Name, $body_text);
$body_text = eregi_replace('%Phone%', $Phone, $body_text);
$body_text = eregi_replace('%Email%', $Email, $body_text);
$body_text = eregi_replace('%Age_Group%', $Age_Group, $body_text);
$body_text = eregi_replace('%Flight%', $Flight, $body_text);
$body_text = eregi_replace('%Cruise%', $Cruise, $body_text);
$body_text = eregi_replace('%Credit_Card%', $Credit_Card, $body_text);

$body_text = eregi_replace('%Best_Price_Net%', $Best_Price_Net, $body_text);
$body_text = eregi_replace('%Best_Price_Full%', $Best_Price_Full, $body_text);

$body_text = eregi_replace('%New_Pick_Date%', $New_Pick_Date, $body_text);
$body_text = eregi_replace('%N_Pick_Time%', $N_Pick_Time, $body_text);
$body_text = eregi_replace('%Pick_Location%', $Pick_Location, $body_text);
$body_text = eregi_replace('%Pick_AP%', $Pick_AP, $body_text);

$body_text = eregi_replace('%New_Drop_Date%', $New_Drop_Date, $body_text);
$body_text = eregi_replace('%N_Drop_Time%', $N_Drop_Time, $body_text);
$body_text = eregi_replace('%Drop_Location%', $Drop_Location, $body_text);
$body_text = eregi_replace('%Drop_AP%', $Drop_AP, $body_text);

$body_text = eregi_replace('%Car_Class%', $Car_Class, $body_text);
$body_text = eregi_replace('%Days_Num%', $Days_Num, $body_text);
$body_text = eregi_replace('%country%', $country, $body_text);
$body_text = eregi_replace('%Employee%', $employee, $body_text);
$body_text = eregi_replace('%Car_Logo%', $Car_Logo, $body_text);
$body_text = eregi_replace('%agencyname%', $agencyname, $body_text);
$body_text = eregi_replace('%Comment%', $Comment, $body_text);
return ($body_text);
}

An example of the html template is http://www.carrentalhawaii.com/html/email-confirmation-us.htmlhttp://www.carrentalhawaii.com/html/email-confirmation-us.html

Seems to me the 100 character buffer is not necessary as the email format is handled within the email itself. So is this the right way...?



$sql = "SELECT * FROM booking WHERE book_id = $book_id ";
$result = mysql_query($sql,$connection) or die("Couldn't execute $sql query.");
$book = mysql_fetch_array($result);

$body = file_get_contents($html_email_template_file);
str_replace(%First_Name%,$First_Name,%body);
str_replace(%Last_Name%,$Last_Name,%body);
str_replace(%Phone%,$Phone,%body);
etc, doing each possible variable one-by-one. I find mysql_fetch_assoc($result); more compact but am not sure how to use it here. Could I say:

str_replace(%First_Name%,$book['First_Name'],%body); or is there a compact way of saying, "Get all the data from the file for that record & replace all the variables in the email with it" using just one statement?
What I am after is a "best practices" model of code I can use whenever I need to get data from a MySQL database, or even from GET & POST variables into an html email. All I have to go on is a very old model of how to do it badly using a method that has been deprecated. I really appreciate your help and skill with the language. It's hard to become fluent in a language when I have to learn it as needed in circumscribed chunks. Mahalo! e :)

djr33
12-17-2009, 07:28 PM
When you need information on a specific function, there will be plenty at php.net:
http://php.net/manual/en/function.eregi-replace.php

According to that, the function ignores case and replaces the content. str_replace will work, or if you do need regex you can use a more complex method (but I don't see why you need that here-- the format of what you are replacing will always be the same, right?).

str_replace returns the changed string. That means you need to use this format:
$new = str_replace($search, $replace, $old);

You can use the same variable for $new and $old, so you are just updating it. But just running the function won't change it-- you need to save it's output back into a variable for later use.

I don't see any reason you can't just replace every instance of eregi_replace() with str_replace() in the code above and it would not work. eregi_replace() allows more options than str_replace, but I don't think any of them are needed in this particular code.

kuau
12-17-2009, 08:07 PM
Dear Daniel:

I had gone to php.net, which is why I am so concerned about the code. Did you notice this warning under the eregi_replace function description?

Warning
This function has been DEPRECATED as of PHP 5.3.0 and REMOVED as of PHP 6.0.0. Relying on this feature is highly discouraged.


Please bear in mind that I did not write this code. I just took over the site to help out a friend after his programmer bailed, and I've been trying to figure out what the code is doing and update it bit by bit. It is a live ecommerce site so I have to be very careful not to break things.

So are you saying I should use the same function but just replace the eregi_replace() with str_replace()? I thought it was bad to use global variables... are these necessary? Isn't there a better way to do this? I have the impression this guy wasn't the greatest programmer in the world so I would be surprised if his method was the most efficient. Is there an example somewhere of the best way to a do a mail merge? Or is this called something else?

Mahalo, e :)

djr33
12-17-2009, 08:18 PM
Global variables are sometimes needed. They only real problem with them is that they are annoying to keep track of, so you may end up with a weird situation where something is or is not global that you don't expect and odd things occur. But in a situation like this, that's fine.
The traditional way to get information into a function is within the function call: function($data,$data2, etc)
But when you need so many variables, and when you know what they will be called in the main part of the script, there is no reason not to use globals.
You could always rewrite it without the function. Functions are generally for chunks of code you will use repeatedly and with variable input. But if this is just a one-time process, you can include it in the main part of the page. That is-- take all the code from the function and move it to where the function is called. But it's not hurting you as is, if you don't mind the use of globals (which is not a problem).

And, yes, just replacing them should work. If it doesn't, try to figure out what isn't matching, but I don't see any reason. eregi_replace uses regular expressions to look for a match. That's fine, and useful sometimes, but here you are replacing a normal string, not something you need to search for with regular expressions.

kuau
12-17-2009, 09:07 PM
The reason I am concerned about globals is because (for security reasons) the default setting for php.ini is Register_Globals = Off;. This is the only one of my sites that has it turned On, so when the host upgrades their php installation and the php.ini gets replaced, the whole site breaks and the owner freaks out (& loses money).

Is it true that if Register_Globals = Off; in the php.ini that using global variables won't work? If so, is there a way to accomplish the variable replacement without setting them to global? Perhaps I don't really understand the difference between regular variables and global variables. I thought it was that regular variables live only in one file whereas globals persist in memory so can be passed between files (?). Is it because the html template is in an external file that globals are needed?

In this application, the code is used in multiple cases so I'll edit the function & see if it works. Thanks so much for helping me understand. e :)

djr33
12-17-2009, 09:42 PM
No, those are two entirely different types of "global" variable.

Global variables are those that have unlimited scope. "Scope" is the area in which a variable is valid. By default, variables are limited to where they are created. They are either available in the page as a whole, or within a function where they are created. global $var; is the method of unlimiting a global within a function. This means that a global in the main script is now available within a limited function (of course this has to be called within every function).

Register_Globals is something different. Some global variables are by default global, rather than using the command "global" within a function. These are generally called "super globals".
Anyway, what Register_Globals does is something very specific:
The arrays from input and server configuration ($_POST, $_GET, $_COOKIE, $_SESSION, $_SERVER) are automatically super globals. When Register_Globals is turned on, then these values are available outside of the array, as variables:
$_X['name'] is then available as $name directly, as a global variable.
In other words, this has nothing to do with your current script because you are dealing with variables you create and making them "global" within a function-- expanding their scope only.

Register_Globals is generally bad, for three reasons:
1. It supports lazy coding. You can use, for example, $username, and not be paying attention to where it came from-- $_SERVER? $_POST? $_SESSION? etc. (Of course this may be "helpful" if you aren't sure where it is coming from, but that's also lazy.)
2. It can confuse things. Let's say you forgot about a cookie stored under the name "x". Then you have another variable in your script named $x, and suddenly those get mixed up and you aren't sure where it's coming from. This annoying Register_Globals setting will mess up your code in a way that is not visible in the script itself, if you aren't expecting it.
3. It can be a security risk, where a variable can be submitted by the user. In the url the user can type ?x=1, and if $x is used to check something for security, then it will be injected as the value for $x in the script. This also applies for mysql injection, where now every variable in your script is a possible security hole if someone knows how to inject values into your queries by just sending get variables in the url (or other similar methods, like cookies).


So: in short, Register_Globals is a pain and lazy coding. Sometimes you need it on if you are using a script someone wrote lazily, but it's more of a problem than it is helpful. But this has nothing to do with the current script. Globals aren't bad-- but making them automatically be generated from all of the input is, because things get messy.
You do point out, though, that this name is very confusing. It should be called "Globalize_Input_Arrays" or something clear like that.

kuau
12-17-2009, 10:11 PM
Yikes! I knew there was something very funky about this code but was't sure what. So the Register_Globals is a totally separate issue I need to fix? I am correct in my desire to turn Register_Globals off? When it is turned OFF all the numbers and dates load into the table as zeroes so adjustments are definitley needed. The code originally had no POST variables at all. I added a bunch, but perhaps not enough. Can this issue be corrected by making sure all the variables are POST variables? I haven't ventured into his date manipulations too much yet as it was over my head.

It is a relief to know that at least this email issue is not affected by the Register_Globals setting so we are narrowing it down. When using a function, can I put it in a separate file and then include it as needed? Do I have to include it every time the function is called? ie. how long do functions stay in memory?

djr33
12-17-2009, 10:40 PM
Yes, it is a totally separate issue. It's not something you "need to fix", but you probably want to. It will work fine with register_globals on, and it may be a pain to rewrite the code so it works without it.
Register_Globals simply takes all the values of those input arrays and makes them individual variables.
So that means that if your dates or anything else are showing up as blank, then you need to use $_POST['date'] instead of $date, or whatever the name of the variable. The problem with Register_Globals is that it does this for ALL of the possible input arrays, so it will be hard to figure out where it is coming from-- the only way is trial and error. Use print_r() to print out the arrays (GET, POST, COOKIE, SESSION, SERVER-- I think that's all of them, and it won't be the last-- that's server configuration stuff), then see which one holds the info. You can also guess, if you happen to know you're using sessions, or submitting a form (get/post), etc.
There is also the option of $_REQUEST which is a combined array that holds the values of $_POST, $_GET and $_COOKIE. Using it will be easier than figuring out where each variable is, or if they may come from multiple sources (like maybe sometimes from a form, other times from cookies, etc), but it also gets messy for similar reasons to Register_Globals, but the difference is that there is no security hole-- it's just not clear if you're using cookies, forms, or whatever, but it will be stuck in $_REQUEST so you won't run into any conflicts with other variables.

Including files is like cutting and pasting the code into your main page. There is no difference* except in where it is stored. (*Rarely, you might have a case where it matters, but this is way beyond anything going on here-- for example, it might be an issue if you are dynamically including pages or something like that.)

functions are defined once and only once-- redefining them will give a (fatal?) error. Of course this is only true for each page load, so it doesn't "stay in memory" beyond just one page, but it will be there for the whole page. functions themselves do not have scope-- they are completely global and always available. Within ANY function, though, only local variables (and those sent in the function like function($var)) will be available. Use the keyword "global" to give the function access to a main variable.

http://www.php.net/manual/en/language.variables.scope.php
There's plenty of info about all of this there.

The real problems are just when you have to figure out what is going on with scope, because it can be very hard to track. Take it step by step and work through the code. That should help you figure things out.

kuau
12-17-2009, 11:58 PM
OK, I updated the files with the str_replace function and so far all is well, so that impending disaster has been averted... Thank you!!

Re Register_Globals, there are no sessions or cookies so I think the loading blanks problem is localized to entry forms and date manipulations, but I'd like to tackle that one after I've finished with the email merge.

Are you saying I can replace this code:


@$fp = fopen($Email_now, "r");
while(!feof($fp)) {
$buffer = fgets($fp,100);
$body.= $buffer;
}
fclose ($fp);

$body = replace_email_template_variables($body);

with


$body = file_get_contents($Email_now);

$body = replace_email_template_variables($body);

Sometimes 'r' was used, sometimes 'rb'... does this matter when I replace it?

Mahalo, e :)

djr33
12-18-2009, 08:17 AM
Glad str_replace() works. If you ever run into trouble in the future, manually compare the search and replace values and find out what doesn't match. str_replace() does not have the complexity of eregi_replace() so there is a chance something won't match up-- but you can figure out what it is by comparing it manually and adjust as needed. I don't see anything that would be a problem in what you posted, though.


If there are no session variables, then you can definitely get all the info using $_REQUEST. It's slightly messier than being specific with $_GET and $_POST, but it's not that big of an issue. When you turn of Register_Globals and you find that a variable is blank, replace $variable with $_REQUEST['variable'] and it will work again. (Of course you'll need to find all of the variables to make it work fully, but it should be fairly easy, if time consuming.) But I completely agree-- if it works, it works, and it may be worth fixing but don't worry about it for now. Go back through once the rest is fixed up.


As for those two bits of code, in short, yes, you can replace it with that.
In detail, there are a couple issues:

1. The only advantage of the original code was that it split at line breaks and controlled line breaks and line length:
--Line length is an issue with emails. The specification officially requires that the format of emails is lines of only 70 characters. This makes emails compatible with even the oldest formats/applications. However, html emails, I believe, are new enough that this should never be a problem. (This is like having valid html... valid emails require 70-character lines... as a practical issue, I don't care much about that, but it doesn't hurt to be careful, I guess. This is why you will receive emails that have strange line breaks that don't make sense-- the mail server chopped the lines at 70 characters.)
--Line breaks are a complex issue within themselves. Different operating systems represent line breaks differently: some as \n, some as \r, and some as \r\n... why? Because competing software companies (Microsoft, Apple, and in this case Linux as well) want to make it harder for web designers (at least that seems the logical reason...). PHP has built in ways of dealing with line breaks and one was used in the original code. file_get_contents() will just dump the original contents into the variable then into the email, so line breaks may not always show up properly. Because of that, you may want to redo all the linebreaks with PHP to be sure they do work. I'm not sure what to suggest for that, because I'm not sure how email is supposed to be done in terms of line breaks, but here's a method: use a php function that handles line breaks and then reverse it. For example $fixed = br2nl(nl2br($old)); -- that will switch all line breaks to <br> tags, then back to line breaks. I'm not sure what is best here, and in the end it is probably best to just leave the original code-- if it works, it works, and there is no reason to change it if those functions are not getting depreciated. My main point in saying you could replacing it was just to explain how it works.

2. "r" and "rb" along with other terms are the ways in which files are opened. The letters stand for different things, like whether you are opening an existing file, or opening/creating a file, or if you're opening it for read only (I think that's "r"), or for writing ("w"), etc. There's a list at php.net under fopen().

kuau
12-18-2009, 06:11 PM
Thanks for all the great info.

1. Seems odd to me that they would deprecate eregi_replace and regi_replace when there doesn't seem to be an equal alternative. Is using the percentage signs in the email text the proper way to indicate a variable, eg. %Comment% ?

2. Would I use $_REQUEST for all the variables in the INSERT command regardless of whether they are POST or calculated variables? I may be wrong but the way I have been doing it is to use POST if the variable comes straight from a form field, and just the variable name if the value has been taken from a form field and manipulated. Here is how I am loading the table (which works with Register_Globals = On but blanks all the dates and dollar values when it is Off):


$sql = "INSERT INTO booking
( `updated`,`country`,`Book_Count`,`Book_Confirm`,`confdate`,`Book_Date`,`Book_Begin_Date`,`Book_Begin_Time`,`Book_Pick_AP`,`Book_End_Date`,`Book_End_Time`,`Book_ Drop_AP`,`Book_Days`,
`Book_Car`,`Book_Comp`,`Book_Pick`,`Book_Drop`,`Book_Age`,`Book_First_Name`,`Book_Last_Name`,`Book_Phone`,`Book_Email`,`Book_Flight`,`Book_Cruise`,
`Book_Card`,`Book_Net_Price`,`Book_Full_Price` )
VALUES
( '$updated', '".$_POST['country']."', '' , 'Not confirmed', '0000-00-00','$updated', '$SQL_Pick_Date', '$N_Pick_Time', '".$_POST['Pick_AP']."', '$SQL_Drop_Date',
'$N_Drop_Time', '".$_POST['Drop_AP']."', '$Days_Num', '".$Car_Class."', '$Best_Rental', '".$_POST['Pick_Location']."', '".$_POST['Drop_Location']."',
'".$_POST['Age_Group']."', '".$_POST['First_Name']."', '".$_POST['Last_Name']."', '".$_POST['Phone']."', '".$_POST['Email']."',
'".$_POST['Flight']."', '".$_POST['Cruise']."', '".$_POST['Credit_Card']."', '$DB_Price_Net', '$DB_Price_Full' ) ";

Seems to me that if you take a POST variable value and assign it to another variable, it should be able to load into the table (?).

3. In all of the emails I am using, the files already exist as external html files - they are a known quantity - that are formatted within a table, so aren't the line lengths already determined and controlled/limited? I'm not sure how php reads a file, but unless you set a buffer length, does it dump it all into one mass of text? Does it ignore html formatting tags?

I did read about fopen at php.net and found it interesting that it lists all the options as r, w, a, and x and then recommends for portability that you use "b" which isn't even an option. Who writes this stuff?!

4. One new question: The old programmer appended the Google Analytics code to each email... does it do any good here, or should I remove it?

Thanks for all your help! e :)

djr33
12-18-2009, 08:55 PM
1. eregi_replace is possible to duplicate with other functions. You could check function_exists() and if not recreate it in other versions. Basically it is just a regular expressions search and replace; I'm not sure which function is best to replace it, but there are a few combinations you could do. I've never been particularly good with regex so I don't use it much.
The %variable% format is just a way of marking it. It is sometimes a standardized format (in the sense that a lot of people use it), but there is no rule that you must write like that. In fact, the % symbol has many uses in various languages, so it can be confusing. But it is rarely used in text in that format, so it is a reliable marker that you won't get conflicts with-- if you are using "%name%" as the marker to replace a name in the text, the odds of having a conflict with text that should actually be output as "%name%" (not replaced) are very low. You can mark it however you want; just use str_replace() to find it and replace it. There's nothing wrong with marking it like that, or you could change it if you want (but I don't see a reason here why you'd need to).

2. $_REQUEST is a combined array of $_COOKIE, $_POST and $_GET-- in other words, all the form variables + cookies... all of the variables sent from the user's computer with the page request. It will never contain variables you create and change during the processing of the script. That is why Register_Globals is confusing. You need to determine which variables in the insert are user-input (use REQUEST) and which ones are from your script (use the normal format). The only advantage of $_REQUEST is not having to figure out if the variables are from cookies, post forms or get forms. That way it will save a bit of time, but it won't eliminate the need to determine which variables are from what, just that you won't have to do it as precisely.

3. PHP reads a file as characters. file_get_contents() returns a string of the file. It does not change, ignore, add, or do anything else to the text. What you have in the file is what you get in PHP: if you were to echo file_get_contents() and nothing else on the page, then you would basically be viewing the file in your browser.
Yes, the line length problem may not be an issue. But it is something you should know about, because in theory it might be at some point. It is also illogical to be replacing after cutting the lines down, because the replacing values (like username) might be very long and make the line too long then anyway. I would say don't worry about it and test the email. If it is working, maybe check on what you can do to make it perfect later, but for now just go with it.

4. No idea. What does google analytics do for emails? There's nothing wrong with removing it, because google analytics just stores statistics. If you don't store the statistics then you won't track something. Unless you need those statistics, there is no reason to keep it. In fact, I think that sounds annoying. But since I don't know what google analytics does specifically for emails, I can't say much more.

kuau
12-19-2009, 01:05 AM
3. You certainly are a wealth of information! I'm going to try the email using file_get_contents() and see what happens. If nothing else, the code will be more compact. I like that.

1. I put the code that replaces the variables in an include and plan to ditch the function unless there is some advantage to using a function (?). There are several other places where replacements are done without the function - I am trying to standardize and eliminate duplicate code so I can get an overview of what is actually going on. Then it will be easier to get a handle on the Register_Globals issue. This guy copied and pasted all over the place... what a nightmare.

4. I'll remove the Google Analytics from the emails. I never liked that.

5. The code always breaks the dates into parts. I'm wondering if there are now newer date functions that did not exist back when it was written (php 3-4) that would help simplify things. Is all this dividing then converting to seconds necessary just to determine the number of days between two dates? I know some people feel that dates should always be stored in unix format, but what if you like to work directly in the tables?


$v_days_diff = (mktime(0,0,0,$_POST['Drop_Month'],$_POST['Drop_Day'],$_POST['Drop_Year'])) - (mktime(0,0,0,$_POST['Pick_Month'],$_POST['Pick_Day'],$_POST['Pick_Year']));
$v_days_diff = $v_days_diff / 86400;

$Days_Num = round($v_days_diff,0);

Thanks! e :)

djr33
12-19-2009, 02:11 AM
1. Functions are only useful for two reasons:
1) it separates and organizes code. This is sorta a misuse of functions, though, and doing an include or just adding comments to your code is probably a better idea.
2) The intent of functions is to not cut and paste code. It makes a section of code repeatable and easy to use with variable input (like function($var,$var2)) when you are frequently doing the same operation. It is in some ways similar to doing an include, but more standardized.

What I do on my bigger projects now is have an extension to the default php function library stored in a main folder, let's say /inc/. So /inc/functions.php is included in every page, and that way I have my standard operations included. For example, in my latest project there are user accounts and in functions.php there are functions like loginuser() and logout(), etc. That way they are always available. If you think you'll use the same functions a lot, or you write your own functions that add functionality to the php defaults, like maybe your own custom version of eregi_replace(), for version 6 and beyond (which is possible), then make an extension using this method and include it in every page.


4. I agree. You can always add it back in later if you need it.

5. If it works, don't do anything to it. Dates are hard to work with. The standard operations with dates are going back and forth from "human readable" dates to timestamps. Timestamps are very easy to store and always predictable. Human readable dates are a little easier to work with and always the format of user input and output. date() and mktime() are the standard formats for working with them. The easiest way to do arithmetic operations with dates is to convert to a timestamp, add, subtract, compare (greater, less than), or whatever else you want, then return them as human readable dates afterwords, if needed. They are fairly easy to work with mathematically: 24 hours in a day, 60 minutes in an hour, etc. Just do the math, and that's it. The problems are usually due to figuring out how to interpret user input and sometimes just getting really long bits of code for seemingly simple operations. That's what's happening here. If you want it more readable, you can separate each step into a different line and make the variable names shorter. This will make more code in the end, though, just in a less cluttered way. But it will still mean rewriting working code, and I don't see much of a point in that.

kuau
12-20-2009, 04:12 PM
Dear Daniel:

1. What do you mean by an "extension" when you say "an extension to the default php function library stored in a main folder" -- a subdirectory? What do you mean by the default php function library? the functions that are built into php and are on the default include path in php.ini? There are some really basic things about php I don't know. I've had to learn it on the fly, piece by piece like a jigsaw puzzle, so things start to make more sense the more I learn, but I still have no idea what the big picture looks like or even how big it is. Your explanations help me immensely.. thank you for your patience & magnanimity in sharing your knowledge and experience.

5. I'm extremely relieved that I don't have to redo all the date code. I guess I just need to understand it. I didn't want to spend time learning a crappy way of doing something but, if what he did is the best way, then I am happy to learn it.

6. When you were helping me with the eregi_replace I discovered at least 9 places in the code where he used either the function or the commands on the same data, but he was not consistent with the variable names... what a nightmare. Am I correct in my efforts to put redundant code into includes? Is there ever a justification for using different variable names when loading data from the same table? I'm thinking (but doubting) it might be necessary in the following scenario:

Data is loaded from a table and displayed on the screen.
Someone then edits that data in a form (eg. adds a confirmation number, perhaps corrects an email address).
The updated data is then put back in the original table.

After the editing, the data in the form should be in $_POST variables, no? I notice he updates the table but does not use $_POST anywhere. In any case, I don't see why the variable names need to be different here when he loads them from the table for admin editing than when he loads them from the table for client editing (eg. to cancel) or to send emails. Am I misunderstanding?

Thanks again for your help! You are a saint. :) e

djr33
12-20-2009, 10:52 PM
1. "extension", as in an extension to the existing code in PHP. If you are frequently doing complex operations with arrays, you might write a bunch of additional functions to deal with arrays, so this would be an extension to the default functions for arrays and you'd want to include them in all pages. PHP has certain functions; you can expand it by writing your own, as you want to. PHP contains (in a rough sense) two parts: 1. the parser; 2. the default functions and other operations. You can expand this by making more functions (and including them into all of your scripts). It doesn't need to be an extension exactly, but you could just make a few functions you want to use often, etc.

6. If you want to, you can write a function for this. That's what the best approach would be. Write a function like dostuff($a,$b,$c), and define this in a page somewhere. Then include that as an extension to the rest of the pages so it is always usable. (In other words, just include this at the top of all of your pages so you can always call the function; if nothing else is in the file but a function definition, then it won't do anything except define an extra function to be available.) Then in each of these instances, you can call that function "dostuff()" with the current variable names and you don't need to change anything. That's the main point of functions-- repeated operations with different variables. function add($a,$b) { return $a+$b; }. You can use that function now to add any $a and $b. Of course you don't need that (since there are built in ways to do it), but that's the point of functions: expanding the default operations to do things you need repeatedly and with variable input. It's entirely up to you. Personally, I wouldn't rewrite the code if it's all working unless there is some need to expand/change in the future-- changing all of that code would be as hard as making it based on a single function (or include), so you might as well go ahead and do that now, if you think you'll be updating/changing the site significantly in the future. There is nothing wrong with not using functions, except that it becomes hard to deal with the code. So that is a personal choice between you and your code-- what's easiest to deal with?

If you get into a situation where it sounds like functions might be helpful, here's how I do it:
1. make main folder called /common/ (or whatever you want)
2. make a file called "functions.php", in which you have the following code:
include dirname(__FILE__).'/name.php';
a) "name" is something like "arrays" or "mysql" or "users"-- the name of the set of functions you'll put there, to keep things organized. You could just write the functions directly into "functions.php" but it will be easier to keep them organized if you have different files for all types of operations.
b) dirname() is used to get the relative location of the files to be included-- otherwise if you include this from two different folders on your site (like the main index.php page, and /contact/index.php), then the relative locations will be off-- so base it on the file's (function.php's) location.
c) repeat that line of code for every extra page of functions you want.
3. In each of those pages, define extra functions that might help you, like my_mysql_connection(), which would automatically start the mysql, or whatever else you can think of that can be automated, like add($a,$b), if you plan to use that a lot, for example.
4. Just then include /functions.php (using a relative path-- like ../../functions.php if you are two folders in) into every single page you may want to call them from.

Of course this is a lot of setup work, but if you plan to have similar operations on many pages this structure is really helpful. No need to use it for this (or any) project, but I like it when I'm working on a big project with lots of repeated php operations.

kuau
12-22-2009, 02:57 AM
Dear Daniel: This is great info! Thanks so much for being explicit. Yes, this is how I like to do things too, modularly with a disciplined structure. The site owner is constantly wanting major changes, so it is worth the effort to clean up the code. I've wanted to bail so many times.

I'm struggling a bit with #2. I looked up _FILE_ and it says... "The full path and filename of the file. If used inside an include, the name of the included file is returned. Since PHP 4.0.2, __FILE__ always contains an absolute path with symlinks resolved"

Is this what you mean? Say I create a file called functions.php that is in the /php directory. Then in my /php/reserve.php file in the <head> section it will say <?php include('functions.php'); ?> and the first lines of the functions.php file will be:


include (_FILE_).'/email.php';
include (_FILE_).'/arrays.php';
include (_FILE_).'/users.php';


Sorry if I am a moron here, but the slash doesn't make sense to me. I would have thought it would be a hyphen. When parsed, does _FILE_ turn into functions.php? Wouldn't it be easier to just write functions.php? This is how I imagine it...


include('functions-email.php');
include('functions-arrays.php');
include('functions-users.php');

Sorry if this went right over my head.

6. I've never written a function before but am game to try.

Many thanks, e :)

djr33
12-22-2009, 04:12 AM
__FILE__ is a constant that contains the path and filename of the current working file. It will be the filename/path of the file that it is executed in, regardless of whether it is included into another file or it is the main file-- this means that when you include functions.php into any file, you will always have the same path.
If you do not use it, here is what happens:
functions.php: include 'numbers.php';
test/yourfile.php: include '../functions.php';
Now it will include it and use that line-- it will look for "numbers.php" in the SAME FOLDER as 'yourfile.php', in other words it will be an entirely different place and the file will not exist.
If you make functions.php like this, it will work:
function.php: include dirname(__FILE__).'/numbers.php';

dirname() gets the directory name that includes the path-- this will be:
[current directory]/....
So that means we add the slash to then add the filename: numbers.php

That line of code is equivalent to: [functions folder]/numbers.php.


You are using only __FILE__, which would be very wrong-- that would do this:
[folder]/functions.php/numbers.php
or
[folder]/functions.php-numbers.php
or something else that doesn't work.


6. Functions are easy and very useful once you get to use them. There's plenty of information out there. They can get confusing sometimes when you are doing something complex, but a basic operation function is not too hard to make.
There are two things to watch out for:
"return" is how you do something with a function-- it "returns" the value and stops the function.
Variable scope becomes an issue in functions-- variables outside the function are not available inside, unless they are global or in the function call (like func($a,$b,...)), and variables made within the function are not available outside it.
Good luck.

kuau
12-22-2009, 05:05 PM
Oh, I think I get what you mean about __FILE__. It preserves the relative position of the included file relative to the file that is including it? The reason I was having trouble was because I always set up an include path in the php.ini file so I never have to even think about the paths... the php.ini takes care of it. That is the way I have done it from Day One. Is there any advantage to using __FILE__ over defining an include path, or can I stay with the way I am used to?

Taking into account the include path, would what I suggested above work?

6. I was trying to set up an include for loading the variables from the table and one for replacing the variables for the email but ran into trouble because of the different variable names in different places. It sounds as if a function works for different variables, and I can see how it works in the second case, but for loading the variables, wouldn't it require as many elements as there are fields in the table? ie. function load($v1,$v2,$v3...$v23). Seems a bit messy, or maybe I just messed up what you meant in my head. :) I need some sleep now. I don't want to exhaust you with my questions. Thank you so much! e :)

djr33
12-22-2009, 08:15 PM
Ah, if you do setup something in php.ini, that will work as well. My method just requires no setup, since I don't have anything like that setup. In general, includes don't need a path relative to another file, but in the case of this sort of project they do. Go ahead with that method.

Yes, if you have many variables the function will be very long. There's nothing wrong with this, but it may look a bit messy.
One problem with functions is that, unless you do a lot of work to get around it, you need to always have the right number of variables in the function call. So if func($a) is valid, func($a,$b) should give you an error. You can allow some variables to be omitted by giving them a default value, so you can do exactly that-- enter one or two, if the second has a default value. But doing that will get very messy with 23 variables.
But if you do need all 23 of those, that's not really a problem-- the fact that it throws an error may actually help you.
Regardless, once you get to that many variables, one way around it is to use an array:
$loadvars = array($a,$b,$c,....,$z);
load($loadvars);
(Now, inside the function you'll have a single array with all the parts.)
But that actually just makes more code for you. In the end, the problem is the number of variables, not the method, really. At some point you're going to have a lot of code.
Using the array will let you add the parts at various times, though, and you can use named parts rather than just numbers:
$loadvars = array('name'=>$a,'address'=>$b);
$loadvars['description'] = $c;
Then all of those parts will be sent to the function when you send the array as the argument.

kuau
12-26-2009, 02:51 AM
Dear Daniel: As we say in Maui, Mele Kalikimaka! Hope you are having a good day with family and friends.

It's starting to sound as if it might be easier just to do a search and replace on the odd variables to make them consistent and then use includes. I am very comfortable with includes but know only a bit about arrays. I am happy to learn arrays, but would rather not do it in the middle of trying to solve this problem unless it is the best way. I have to learn Magento right now too so am not getting much sleep. Please tell me which way you would recommend (function, array, include) as the best way to proceed. It's hard for me to make an informed decision when I am not conversant with 2 of the options. I really appreciate your help & guidance. Mahalo plenty! e :)

djr33
12-26-2009, 06:13 AM
Aloha and happy holidays to you too.

There is always some logic with just doing what you are familiar with-- there's less chance it will break in a way you can't fix.

I think it would be possible in this case to use includes.

Arrays are not needed. If you know what you're doing with arrays, you could save a bit of mess in the way the function looks in the code, but that's about it. You could also setup the function over many lines of code, rather than just doing it in one. Basically, it would make using a function more convenient. It wouldn't be required.

A function would also be a way to make it easier to use once it's all setup. You don't really need one. There's no absolute benefit to functions, except that to someone who knows what they're doing with functions the method might be a bit cleaner.

Try to go ahead with includes. If you get it to work, that's all that matters. If you end up with problems, then you may want to try using a function instead.


The real difference between using includes (or any other methods) and using functions is that functions are more predictable sometimes: includes can get messy, but functions will almost always reliably do the same thing each time. Includes may or may not do exactly the same thing, if they are included in various parts of various pages. Functions are self-contained routines. Includes just grab code and place it into the mess of existing code, so things can get messier there. For example, if you have a variable named $x in the main script and you do an include, then it will conflict with a variable $x in the include. If the variable $x was in a function, there would be no conflict-- functions are basically self-contained.

kuau
12-26-2009, 07:39 AM
Well I must say you have put my mind at ease. Thank you for that. Now I can go running and relax a bit. I think I understand now about the scope of the variables in functions as opposed to variables in the script. Is it true that declaring variables as global within a function increases their scope to the same level as other variables in the script? If that is true then I can feel much less intimidated by his code now that I can see the different parts to it and what they do.

One thing that his code doesn't do is prevent invalid dates. People can enter dates like 09-31-2009 or 02-30-2010. Is there an easy way to test for a valid date? Some blank records are ending up in the table and I am wondering if this may be a possible source. I know spam bots can cause this but there has never been a CAPTCHA on the form and it started happening only in the past 6 months after I "improved" some other parts of the code. Have to go run now.. thanks so much! e :)

djr33
12-26-2009, 07:56 AM
Yes, basically. Global variables in a function are elevated to a main variable's level.

There are ways to limit what dates can be entered. The easiest way is to convert it to a timestamp then have an upper and lower limit on what that can be. There's no default way, so you'll have to code it yourself.