Using Transfer Files in PHP, their purpose, implementation and why they might just save your sanity.
At some point, virtually all PHP developers will find themselves in a situation where they are passing values from one PHP script to another. This article focuses on the benefit of using a Transfer File to handle the data exchange and separation of the logic from the display and core processing scripts. Before we jump right into what Transfer Files are, let’s first consider a familiar example that most of us will have come across at some point or other: a user registration page. This page collects information about the user and then submits it for validation and processing.
Here is where the first issues arise: which script is responsible for the validation of the form data and what happens should this validation fail? How can we return the user back to where they started, with the original form re-filled with their data even if they just click the “Back” button in their browser? More importantly, what do we do if the result of the data has multiple potential destinations?
As programmers we are also, whether we like it or not, responsible for creating user friendly applications. This means not relying on browser specifics to achieve tasks and hand-holding the user through our systems as much as possible. A good example of a badly designed user experience follows:
Joe Bloggs enters his username and password into a web site login page and submits the form.
The script checks his data and sees that something doesn’t quite match, so it sends him to an error page that simply says “Username/Password invalid” and urges him to click “Back” in his browser and try again.
This is more common than you might think and several high-profile web sites adopt this principal of “let the user fend for themselves”. But poor Joe is sat there thinking “Well which was it? My username or my password that failed?” and when he clicks “Back” in his browser he finds the form is now blank and he has to try again.
This might not sound like such a big deal, but what if the form had been part of a registration process or a shopping cart purchase page? Often the forms in use are both far more complex than our example and have multiple outcomes based on the user data. As we all know, the more data you loose for someone, the bigger their frustration factor. Using an XT (Transfer) script is one way to allow you to keep track of this information flow and to separate the logic of your site from the display and core functions.
So what is an XT script and how does it work?
XT stands for “Transfer” and there are only a handful of special features about XT scripts to distinguish them from any other PHP script:
- XT files have no output at all, they should never echo or print values to the browser.
- XT files by their nature are transparent to the browser. The browser will never know that the form data went through one. This circumvents our browser “Back” button issue.
- XT files traditionally start with xt_ in the filename (i.e. xt_login.php) so you can immediately distinguish them from standard PHP scripts. In more complex sites you can even group them together in an xt directory.
- XT files are usually very short in size; they are merely transfer mechanisms and as such rarely handle the data in any great depth.
At the most basic level an XT script does the following:
- Collate all required values (from POST, SESSION, COOKIE, etc)
- Validate the existence of required form fields.
- Pass data to the function/s that needs it.
- Redirect the browser accordingly, based on the function/s results.
We will cover the use of an XT script in a basic login form situation.
Example – A Login Script
We’re going to build a very primitive login script for a web site. The first page will simply be a form that requests a user name and password. This will then be sent to an XT file that will redirect to either the login form again with a suitable error message, or to a success page. Our desired system flow is outlined in Figure 1.
Listing 1 – login.php
[geshi lang=php]
Login
$error";
}
?>
[/geshi]
Listing 2 – xt_login.php
[geshi lang=php]
login($username, $password, $persist);
if ($result !== TRUE)
{
Header("Location: login.php?e=$result");
exit();
}
else
{
Header("Location: welcome.php");
exit();
}
?>
[/geshi]
The XT script in Listing 2 is split into three identifiable parts:
1. Form field checks and assigning data to variables
At the start of Listing 2 we set the variable $core_error to be false. The $core_error variable is used in conjunction with the if (isset()) blocks of code that determine if the form fields exist or not. Please note carefully what we are doing here – we are not checking if the user entered any text into our login form, we are checking to see that the form fields were actually on the page that POSTed to our XT file.
If a form field was incorrectly named or missing from the form it will raise an error. This will work for text inputs, password inputs, textareas, select lists (where there is at least 1 option defined) and radio buttons (where there is a “checked” button). It will also work for submit and standard buttons that have been assigned name values. Due to the way in which they work checkboxes need to be tested differently as shown on lines 23 to 30 of Listing 2. The fact that the checkbox doesn’t exist cannot raise a core error because it simply means the user didn’t tick the box.
By the time our three checks have completed we can be safe in the knowledge that all of the form fields that should have existed did and that the variables we now have ($username, $password and optionally $persist) all came from the POSTed form data and no-where else. This doesn’t mean that you can be sure the form submitted to your XT file was from your site, but it is beyond the scope of this article to cover form security; suffice to say any further checks you need can take place at the start of the XT file.
2. Data pass-through
Now we have our data stored in local variables we can pass it to our function to deal with. In Listing 2 you’ll see that the variables have been given to a user object function called login. It is important to stress that in an ideal world, XT files shouldn’t be responsible for the validation of form data. Your functions should be the ones that validate the data and return results accordingly. The reasoning behind this is to remove the element of risk and error from your code. For example if your user login function assumed that the data it was being sent was always pre-validated, this could open you up for problems if you ever called that function without passing the data through this XT script first. It is the role of the XT script to give the data to the functions that require it and handle the results accordingly, not to be responsible for the integrity of the data.
3. Redirection
Based on the result of the login function we can redirect the user accordingly. We use the PHP Header function to achieve this. The user is going to be taken either to the login form again or to the welcome page of the site. In this simple example there are three possible error outcomes from the login function:
- Username not found in the database
- Password was incorrect
- Account has expired
It is important to note that the user will never see any of the redirection actually happen. We’re not using page reloads or anything of the kind, all of the redirection is transparent even to the browser and that is the key behind retaining the form state as demonstrated later in this article.
Far from perfect
Although this example serves as a good demonstration of the function of an XT script, it isn’t the most desirable way to achieve the result we want and can be improved with a few small but significant changes.
Improvement 1:
If the login function just returns a number there may be issues later on in the sites life in knowing what each number relates to. The easiest way to address this would be to use a language file include. Here is a small part of an English language file in use on a live site:
[geshi lang=php]
$user_txt[15] = "Please enter a Password.";
$user_txt[16] = "Password was too short. Minimum of 5 characters.";
$user_txt[17] = "The two Passwords did not match, please re-enter.";
[/geshi]
We’ve got an array called $user_txt and each element of the array is a different English language error message. In this case we can see errors 15, 16 and 17. At the moment our login.php script has the error message text hard-coded into it which is resulting in a quite lengthy switch block at the start of the file. We could easily replace that entire block with the following had we used a language include file:
[geshi lang=php]
if (isset($_GET['e']))
{
$error = $user_txt[$_GET['e']];
}
[/geshi]
You can strengthen this even further by placing a simple array key check to determine if $_GET[‘e’] actually exists in the $user_txt array before assigning it to $error.
Improvement 2:
The error return code is passed via the query string (GET method) which means anyone could modify it to see what happens.
While our checks at the start of login.php will prevent anyone from entering random error values, it does mean they could (if they were bored enough!) go through the entire language file reading each error message by altering the number in the URL. While some of you might not be too concerned about this, the solution is simply to place the error return code in a session variable instead of in the querystring. The user cannot then determine the error code from the querystring; they merely see the login page again with the error message present.
There are other extended checks you can implement such as verifying that the error code is numeric, or at the start of the login.php page you could hold a list of all of the valid error codes for that page, so anything else requested outside of that range is ignored. Finally your language file could have one language array per page, so instead of a $user_txt array containing all possible errors for the user object functions, you could store them in a $login_txt array and limit them to those errors applicable to the login process only.
Improvement 3:
With a small login form like this we can have the XT file handle each form element separately. But what if the form had 30 different fields on it or more, that would be an awful lot of “if isset” blocks. We can modify our XT file so that it loops through an array looking for the different elements it requires. Listing 3 demonstrates this loop.
Listing 3
[geshi lang=php]
[/geshi]
So we’ve cut away all of the clutter, optimised our XT file and decreased the chance for errors by checking and validating data as it is sent from one script to another. This is a great situation to be in, but you’re probably still thinking “why bother?” Figure 2 shows a far more complex, although no less common use for an XT file.
In Figure 2 we have multiple possible outcomes for a single XT file. The XT file hasn’t actually checked or verified any of the checkout page data, it has merely passed it to the functions that do and based on their results (and the results of other functions) it knows where to send the user next.
Without an XT file in this scenario where would the checkout page actually submit its data to? Would it be the order success page, which in turn would then have to think “hang on, this user isn’t even registered, let’s redirect them to the new shopper area” as well as handling the other possibilities on display. The XT file is like a junction point on a railroad, directing the data (and consequently the user) in the right direction at all times.
Retaining form state via sessions and XT files
Another extremely useful benefit of XT files are their ability to retain the state of a form even if the user sits there clicking “Back” in their browser. Traditionally if the user did this, most of the time the original form will be emptied out causing them to have to enter it all again. This is especially true of forms sitting on dynamically generated pages. But there is no reason for this to happen and it’s possible to tailor the users experience accordingly.
When your data enters the XT file it is passed to a function that returns error codes. These functions could return an array of errors if there was more than one thing wrong with the data.
The correct data should be placed into the user’s session and the error messages also. For example the section of code in Listing 4 comes from a forum implementation:
Listing 4
[geshi lang=php]
$message = new Message();
$result = $message->Post($boardid, $body, $sourcecode, $message_subject);
if ($result !== TRUE)
{
$_SESSION['error'] = $result;
$_SESSION['message_subject'] = $message_subject;
$_SESSION['body'] = $body;
$_SESSION['sourcecode'] = $sourcecode;
$_SESSION['boardid'] = $boardid;
Header("Location: http://$http_host/?m=forum_read&i=$boardid#post");
exit();
}
else
{
$_SESSION = array();
Header("Location: http://$http_host/?m=forum_read&i=$boardid");
exit();
}
[/geshi]
Here we’ve posted a message to a function that either returns TRUE (on success) or an error array. Upon an error the session is loaded with the variables + error array and the user is returned to the original message posting page.
The trick is that the original page the user came from has PHP script at the top of it that checks for the existence of the $_SESSION[‘error’] value. If found it displays a slightly modified version of the post form that includes the error message/s at the start and the areas that are incorrect highlighted in a different colour. All the rest of the form data is retained.
That is the key to this method - the original form actually has support built into it for handling error messages and returned data by checking the users’ session, it’s intelligent enough to know how to modify the layout to make the experience a friendlier one. You can also use just one script per form rather than the initial one and then another for handling errors.
Without using an XT file in this manner you would have to direct the form either back on itself again or to another location that could deal with the error handling. In both cases the smooth flowing structure we were trying to create at the start is broken, especially on more complicated conditional branches.
Conclusion
XT files are just another means to an end, but they are ones that I personally have found a complete life saver in the years I’ve been using them. They’re single minded in the task that they perform, they provide an easy means of forms retaining state and even better – they’re extremely easy to implement in a site diagram and flow structure. If anything, they enhance the flow of data and other developers understanding of it.
Note: This article appeared in International PHP Magazine back in 2004 and was written for PHP4 - please take this into consideration while reading.
You can download this article in Microsoft Word format: From-A-to-B-via-XT.zip (17 KB)