About this page

This page describes my learning experiences with PHP through the design and implementation of a Web simple application for recording income and expenditure: SCaB, the Simple Cash Book. This page does not describe how to use the SCaB application; rather, it describes how I wrote the SCaB application and why I made the design decisions I did. It is intended for readers who have some experience with programming (though not necessarily in PHP) and a basic knowledge of Web development.

The purpose of this project was entirely pragmatic, in that I wanted to create an application to track my personal and business expenditure. Writing the application in PHP gave for a good opportunity to try my hand at Web application development in general and PHP programming in particular. I did not set out to create a general-purpose Web development framework, though many of the techniques I explored while writing SCaB could conceivably be re-used in other projects. However, the project is definitely “exploratory” in nature despite its real-world requirements. In other words, there are certain features in SCaB that I could have lived without but which I implemented anyway because they allowed me to explore a particular aspect of PHP or MySQL programming. Some of these features really only exist as proofs of concept and would need to be developed further if SCaB was intended for use by a general audience. Likewise, there are probably a number of missing features that a casual user (or a professional accountant) would regard as indispensible. For example, SCaB is very much, as its name suggests, a cash book. It has no facility (as yet) for producing financial statments like Balance Sheets.

Why SCaB?!

It’s often the case that application programs are given ridiculously self-aggrandising names (like Excel or WordPerfect), which the application often fails to live up to. Or they may be given names so vague as to be utterly unmemorable (usually involving some permutation of the words “Information”, “Management” and “System”). The “official” name of the application whose development is described in this document — Simple Cash Book — probably falls squarely into the “utterly unmemorable” category. Changing SCB to SCaB seemed like a bit of fun: deliberately picking name that was anything but ridiculously self-aggrandising. Also, when the author was growing up in Dublin, to call someone a “scab” was a grave insult: a “scab” was a miser, a person who wouldn’t share their sweets or toys or money. The kind of person, in other words, who was likely to count their every penny, much as the application was designed to do. And thus was born a name that was somewhat apt (assuming one was au fait with Dublin slang) and a good deal more memorable, if perhaps a little nauseating. The author apologises for any distress caused by his strange sense of humour.

Contents

Background

SCaB is a simple Web-based application for recording income and expenditure. It is written in PHP and uses MySQL to store its data. SCaB has been run successfully on PCs running the Linux operating system and the Apache Web server.

What is PHP?

PHP is, according to its manual[1], a widely-used Open Source general-purpose scripting language that is especially suited for Web development and can be embedded into HTML.

Although PHP has enough general purpose programming constructs to be used for generic system-administration tasks, its roots are in web development.

History of PHP

The first incarnations of what was to become PHP were written by Rasmus Lerdorf. At this point, PHP was not a programming language in its own right; rather, it was a set of Perl scripts that Lerdorf used to help him maintain his personal home page (hence the origin of the name) Later, Zeev Suraski and Andi Gutmans rewrote PHP as a standalone language. Suraski and Gutmans formed Zend Technologies, a company that produces commercial PHP products.

Features of PHP

PHP is a modern scripting language with a number of powerful features:

In the past few years, PHP has seen great success. It has come from being regarded as a rather obscure hobbyist language to a solid web development platform running some of the world’s most popular Web sites, with the support of many large companies. I believe that there a number of reasons for this:

Criticism of PHP

Although PHP has been successful, it hasn’t been without its critics.

Early versions of PHP were heavily criticised for poor security. A feature of PHP called “register globals” made PHP easier to learn for beginners but made PHP scripts vulnerable to injection attacks. Newer releases of PHP have tended to disable this feature by default.

Some criticisms are the result of design decisions made early in PHP’s history, decisions that made sense at the time but are increasingly difficult to justify: the lack of any namespacing in the extension system (which made sense when extensions were few); the assumption of an 8-bit character set (which made sense when most PHP programmers lived in the US or Western Europe); the fact that many extensions are not thread-safe (which made sense because multithreaded programming on Linux was still something of a novelty back when PHP was first written). Most of these specific issues are being addressed or will be in future versions of PHP.

PHP’s incremental philosophy, which allows new programmers to pick up the language with little effort, has had its own downsides. Many PHP programmers will have learned the language pragmatically, absorbing just enough to get the job at hand done and having little interest in building consensus on “best practices”, though these are starting to take shape with initiatives like the Zend Framework. However, I believe that there is something of an inferiority complex at work in the PHP community. Many PHP programmers are self-consciously aware that PHP is looked upon as “amateur” by the users of more so-called “professional” languages like Java and there is a strong pressure in the community for PHP to adopt features from these languages even if they don’t fit into PHP very well.

What is MySQL?

MySQL[6] is a popular Open-Source relational database management system available for Linux, Windows and other operating systems. It was written by Michael “Monty” Widenius, a Finnish programmer who works for MySQL AB, a commercial company that offers support and other pay services for the MySQL database. The current release of MySQL is version 5 but version 4 remains in widespread use. Early versions of MySQL lacked certain features found in other RDBMS systems (for example, subqueries, transactions, views, stored procedures and triggers) but these have been added in later releases; despite these shortcomings, MySQL rapidly became the database system of choice for many Open Source projects and is used by a large number of popular Web sites.

The design of SCaB

Basic design goals

These goals crystallised from hunches or gut-feelings I experienced as I explored the language, based on my own experiences with other programming languages and with other requirements. Some would be PHP-specific, while others would apply to Web application design in general. As such, they are somewhat personal and not to be regarded as being carved in stone:

Redirect after POST

In the original HTTP specification, it was recommended that GET requests were used to query data on the Web server and POST requests were used to modify the state of the server. Many Web applications do not follow this recommendation (which can lead to disaster: there are horror stories of Wiki-type websites destroyed by visiting search engine spiders because each page contained a “delete this page” link implemented as a GET request). Even Web sites that use GET and POST correctly can malfunction if the POST request returns a page of content. If the user moves back and forth in their browser history, the POST request can be submitted multiple times to the Web server (though most modern browsers will display a rather cryptic alert before posting the data again)

To avoid the problems with POST, some Web developers have recommended using a technique called “redirect after POST”[7]. As the name suggests, the Web application does not send any data in response to a POST request; insteads it redirects the browser to a new page. The advantage of this technique is that it doesn’t break the browser's back button or cause strange warning messages to appear.

The way SCaB implements redirect after POST is illustrated below.

Diagram of redirect after POST

Each kind of entity in the SCaB database (accounts, transactions, etc.) has a three- or four-letter identification code (“acct” for accounts, “trn” for transactions, etc.). By convention, the PHP script that displays a list of a particular kind of entity will be called list_xxx.php where xxx is the corresponding identification code. This script will normally display a table of entities with a checkbox to allow entities to be selected for modification and a series of action buttons to select the kind of editing action (most entities support the creation of new entities or modification, duplication or deletion of existing entities; some entities allow more) is to be carried out on the selected entities. When the user clicks the chosen action button, the browser issues a GET request for the get_xxx.php page. This page displays the appropriate form for the selected action, storing the URL of the calling page as a hidden field in the form. When the user clicks the OK button to confirm their changes, the browser issues a POST request for the put_xxx.php page. This page updates the database appropriately and then redirects the browser back to the stored URL.

Other design issues

Some of the ways in which SCaB developed did not come about as the results of conscious design decisions but were consequences of my own familiarity with PHP, the versions of PHP and MySQL that were current at the time and the kind of application SCaB is (that is, a small application with few complex features intended for use by a single person or a few people at the most). These are some of these kind of issues:

Application data model

The SCaB database can contain multiple account sets. Generally, a user will be working with a single set of accounts at any one time. Each set of accounts is further subdivided into account groups (for example, Income and Expenditure). The accounts themselves have transactions posted against them. SCaB is a double-entry accounts system so each transaction debits one account and credits another.

This is the basic account-handling functionality in SCaB. In addition, there are the following features:

This is illustrated in the diagram below (some fields have been omitted from the tables for clarity)

SCaB tables

Users and permissions

As an application intended for use by a single person or a few people at the most, SCaB has a fairly simple system for authenticating users and ensuring a user has permission to access a particular entity in the database. This system is fairly generic and could be adapted for use in other applications but is more of a proof of concept than a fully-fledged general-purpose security mechanism.

SCaB understands two levels of permissions: global permissions and set-level permissions. More levels could be added if needed — for example, down to the account or even transaction level — but I decided that that would be too fine-grained for such a simple application. These permissions are stored in two different tables in the database but are merged into a single array in the user’s session when they log in or change to another set.

As was mentioned in the basic design goals, SCaB uses a declarative approach to security. The following security functions are provided:

The .down file

A useful feature of any Web application is the ability to lock users out of it so that maintenance can be carried out (for example, a database backup). This can be done in a brute-force way by shutting down the Web server but this is often not possible if the server is shared and is quite user-hostile. SCaB provides a very simple way of shutting it down: if a file called .down is placed in SCaB’s directory, any users attempting to use the application will be redirected to a warning page indicating that the application is offline. The existence of the .down file is checked when the require_login() function is called so that content that does not require an explicit login (for example, online help) would remain accessible during the outage.

The .down file does not have to contain any information but a short message can be included in it which will be displayed on the warning page that the users see if they try to use SCaB while it is offline. The idea is that system administrators would be able to run scripts like the following:

#!/bin/sh
# Backup the SCaB database
# Take SCaB offline
echo "The database is being backed up. Try again later. " > /var/www/scab/.down
# mysqldump ...
# Allow users back into SCaB
rm /var/www/scab/.down

Handling multiple selections

A set of accounts in SCaB can contain a large number of transactions, perhaps thousands in a single year. Because of this, it was important for SCaB to be able to edit multiple entities simultaneously. Also, as many transactions are similar (differing perhaps only in the date of posting), it was convenient to have a way of duplicating transactions.

The ability to edit multiple records at once was handled in the following way:

The record duplication feature works best on tables that have an auto-incrementing primary key as it is difficult to ensure the uniqueness of the key otherwise.

Error handling and logging

Error-handling is an important part of application design. There are two kinds of error that an application can encounter:

(This ignores the all-too-common application errors caused by programmers themselves!)

A good error-handling system must indicate to the user when they have made a mistake and how to go about fixing it. In the case of system errors, the error-handling system should attempt to store as much information about the problem as it can to help diagnose and fix the problem.

User errors

SCaB has a function, show_error_page(), that redirects to an error page. It takes an error code as an argument.

Logging the error

PHP provides a function called set_error_handler() that allows a developer to specify a function that is called whenever an error occurs in code. SCaB provides its own error handler which writes as much information about the programming environment. The information is written to a file on disk in an XML format (though it is not a valid XML because it lacks a root element) The file is located in SCaB’s home directory. There are scripts in SCaB to display the error log as HTML or as an Atom[11] feed. There is no facility within SCaB to archive or prevent the log file from growing very large; this should be done with logrotate[12] or similar.

The error log can contain sensitive information like the username and password used to log onto the database because all information about the PHP environment is stored when the error occurred. Also, because the error handler cannot depend on the database being up when an error occurs (in fact, the very reason that the error occurred might be the database suddenly going down), it does not use the database-based authentication mechanism used by the rest of SCaB. For this reason, the .htaccess file in the SCaB distribution denies access to the “view errors” pages to anyone and this functionality is not available in the online demonstration. It is up to the system administrator who should be allowed access these pages and to implement an appropriate access-control mechanism (e.g. by using the authentication mechanisms provided by the Web server or limiting access by IP address). Screenshots of the HTML and Atom views of the error log are shown below. The Atom view is displayed using Sage[13], an RSS and Atom feed reader for Mozilla Firefox.

Screenshot of HTML view of the SCaB error log

Screenshot of Atom view of the SCaB error log using Sage, an RSS plugin for Mozilla Firefox

Known issues

SCaB will not work properly with versions of Internet Explorer earlier than version 7. This is a result of a long-standing bug in Internet Explorer [13] in the way <button> elements are handled in forms. This is a good example of a bug in SCaB that would be unforgivable in a “real” Web application but which I was willing to live with (for the simple reason that I don’t use Internet Explorer)

Also, SCaB insists that all dates be given in the format YYYY-MM-DD. Programmers will be familiar with dates in this format as it is similar to the ISO8601 format used by XML and other standards; however, most general users will probably find it annoying that they cannot enter dates in a more familiar format.

If the user submits a form containing data that causes the database update to fail and then clicks Back to fix the problem, the updated values they entered will be lost because the “get” page is not cached. Although most validation problems will be caught if the user has JavaScript enabled in their browser, this is not very user-friendly.

SCaB also relies on the HTTP “Referer” (sic) header to be set so that the user’s browser redirects back to the original page after adding or editing a database record. Some proxy servers remove or otherwise interfere with this header, making the experience of using SCaB from behind one of these proxies less than optimal.

Future directions

SCaB exists to serve two purposes: to help me keep track of my spending and to act as a testbed for exploring PHP. As a result, its evolution tends to be rather. However, there are a number of parts of SCaB that would make good candidates for improvement.

  1. Fix problem with buttons in old versions of Internet Explorer.
  2. There is probably a lot of redundant code in the list_xxx.php, get_xxx.php and put_xxx.php scripts that could be cleaned up. A set of scripts to generate skeleton versions of these pages (similar to the “scaffold” feature in Ruby on Rails) would be a good addition.
  3. A number of features are still missing or incomplete as they are rarely used. These include: creating and deleting account sets and groups, and editing user permissions.
  4. Remove all hard-coded strings from JavaScript to facilitate localisation (for example, by using PHP’s gettext library)
  5. Modify the code to take advantage of the new features of PHP5. For example, it may be possible to replace the db_connection class with a PDO object, thus reducing the amount of custom code in the application and making the code more database-independent. However, migration to PHP5 is impeded by the slow take-up of PHP by hosting services.
  6. Add support for the posting of recurring transactions such as direct debits and standing orders. Much of the code for this feature already exists in SCaB but its implementation has been hampered by difficulties in providing a satisfactory user interface for defining and modifying the recurrence rules.
  7. Add a reporting feature to generate, for example, Income & Expenditure/ Trading Profit & Loss and Balance Sheets. This would be a good way to explore the options for generating PDF files with PHP.

References

[1] PHP manual, Introduction, http://www.php.net/manual/en/introduction.php
[2] Ruby on Rails, http://www.rubyonrails.org
[3] ASP.NET, http://msdn2.microsoft.com/en-us/asp.net/default.aspx
[4] PHP Manual, http://www.php.net/manual/en/
[5] PHP Links, http://www.php.net/links.php
[6] MySQL AB Home Page, http://www.mysql.com
[7] AJAX definition, http://en.wikipedia.org/wiki/Ajax_%28programming%29
[8] Redirect after POST described, http://www.theserverside.com/tt/articles/article.tss?l=RedirectAfterPost
[9] Front Controller pattern described, http://c2.com/cgi-bin/wiki?FrontController
[10] Zend Framework, http://framework.zend.com/
[11] RFC 4287, Atom Syndication Format, http://tools.ietf.org/html/rfc4287
[12] logrotate, a utility to compress Unix log files, http://iain.cx/src/logrotate/
[13] Sage browser extension, http://sage.mozdev.org/
[14] Form submission bug in Internet Explorer (search for “BUTTON elements”), http://channel9.msdn.com/wiki/default.aspx/Channel9.InternetExplorerProgrammingBugs

Document history

VersionAuthorDateComment
1.0 Ken Keenan 07 August 2007 Initial version