PHP Application Development Part Two

Building on what we learned in part one, this article discusses data storage, system configuration, error handling and logging. Read on to find out how practicing the proper use of these concepts can simplify application maintenance and testing, and accelerate system debugging.

In the first article of this series, we covered some fundamental PHP application development concepts. Directory structure, file naming, and basic coding conventions were discussed. If you missed that article, you can find it here (http://www.devshed.com/c/a/PHP/PHP-Application-Development-Part-One/). In this article, we continue with the basics of PHP applications by discussing application configuration, data storage, logging and error handling. We will cover some of the basic considerations when approaching these issues and evaluate our options in regard to implementation, efficiency and maintainability.


With any application, there is a need to centralize certain information. Things like database credentials, file paths, constants, and initialization variables and options should be isolated from working code and managed in as centralized a way as possible. This allows you to alter these values in a centralized location rather than across any number of files.

Centralizing Your Configuration 

The need for centralized configuration is critical in any application, and is quite possibly the single strongest contributing factor to code maintainability. There are a number of approaches to this issue; it presents a particular problem when evaluating flexibility versus efficiency. The most typical methods for storing configuration files are with standard PHP files, XML files and INI files. Examples of a configuration file that contains only database credentials would look like the following.

 PHP
 <?php
 $settings[‘db’][‘user’] = ‘root’;
 $settings[‘db’][‘pass’] = ‘’;
 $settings[‘db’][‘host’] = ‘localhost’;
 $settings[‘db’][‘name’] = ‘test’;
 ?>

 INI
 ; settings
 [db]
 user = root
 pass =
 host = localhost
 name = test

 XML
 <settings>
  <db>
   <user>root</user>
   <pass></pass>
   <host>localhost</host>
   <name>test</name>
  </db>
 </settings>

These files are fairly straightforward, each containing a username, password, hostname and default database. Implementing the PHP file is simple enough – simply include it with the “require_once” function and no further processing is required, the “$settings” array is ready to use. The PHP version is also the least maintainable, in that it looks just like any other code and is not especially easy to read.

Using the INI file is not quite as simple, but requires only using the “parse_ini” function to retrieve the values from the file into an array. The INI file is much easier to read than the PHP version, but it could still be better. The XML version is by far the slowest, requiring at least some string matching and at most parsing the entire file. The tradeoff is evident in the semantic, easy to read nature of XML. XML files are also easily managed by a large number of programs, require the least explanation for other developers to implement, and offer the greatest portability to other modern scripting languages. This means that configuration could be shared by Java, Python, .NET, or Perl applications, for example, with ease.

Realistically, the processing time between each of these files is almost entirely negligible. However, as configuration files grow to store other information, that changes. For the most scalability and raw speed, using a PHP configuration file is the best bet, but I personally advocate the use of XML. The tradeoff for maintainability is worth it so long as you minimize the amount of XML parsed on every script page.

This is simply my opinion, and any of these options will do. In addition to using a variable to hold system settings, you have the option of storing settings in constants. Deciding what data should be stored as a constant and what data should be stored in a system variable is mostly a matter of preference, though I prefer to store system configuration in a settings array or object, and system messages and frequently used values (such as the number of seconds in a day or the maximum dimensions of uploaded images) in constants. The following list contains some common information to store in configuration files.

  1. Database credentials
  2. File paths
  3. System strings and error messages
  4. Constants for frequently used system values

Centralizing important system information is critical to the maintainability of any application and is invaluable when handing off a project to a new developer, because understanding how system data is managed is one of the first steps in understanding an unfamiliar application.

{mospagebreak title=Storing Dynamic Data} 

Having established the basic rules for handling configuration, we should now evaluate or options for storing dynamic data in our application. Dynamic data consists of any information that is expected to either change often or be manageable through a Web interface, or both, as is typically the case. In a typical Web application, this would consist of things such as text for Web pages or items to appear in a menu. This could of course be extended to include any sort of data with which your particular application works.

There are only two options available for storing dynamic data: the filesystem itself or a database. Storing data in a database is by far the most common choice in recent years with the advent of easy to use databases like MySQL. While I use MySQL for nearly every project I build, there are still times where storing data in a file simply makes more sense. There are a few quick questions you can ask yourself if you aren’t sure which is appropriate for your situation.

  1. Will the application need to produce reports comprising aggregate pieces of data from the site?
  2. Will the application need to store meta data for persistent objects?
  3. Will the application need to maintain strong relationships between persistent objects?

If the answer to any of these questions is yes, then a database is probably the logical choice. Typical justifications for using a database include “this website needs a search tool” or “this website needs to store data in multiple languages.” Oddly enough, neither of these are reasons to use a database, and in the case of storing text in multiple languages a database can often complicate the situation. Web application searching can be achieved with free tools like htdig and multiple languages can be handled simply by good file system orgnization.

Databases really show their strength in producing aggregate data, storing metadata, and storing logical relationships. It is true that you could build reports from data stored in flat files – after all, that’s how it was done before databases were around – but why reinvent the wheel? SQL provides a consistent semantic method for producing reports; by all means, use it.

Maintaining metadata is also possible with flat files, either by maintaining a metadata file for each persistent object or by combining the metadata and the actual data into a single file using XML or some custom encoding scheme. Often the best solution is to use a combination of the file system and a database. Consider a system where users should be able to upload images. It is far more efficient to store images in the file system, but storing metadata in the database allows you to normalize your data and search image metadata without clunky text file processing. In the case of a system that stores text documents, it would make sense to store the metadata and the actual text in the database and avoid flat files altogether. This simplifies management, boosts maintainability, and simplifies feature changes such as adding support for versioning, authors, publishing state, and so forth.

Databases also shine when used to store relationships between persistent objects. SQL allows you to easily build a result set containing individual records for objects spanning multiple tables or even databases without writing a lot of XML parsing code, messy object serialization or even worse, the use of some proprietary encoding scheme. This task can be accomplished in the filesystem with either method, but neither one is robust or elegant. Portability and semantics are completely lost when this sort of information is stored in a serialized or proprietary format, and while XML does maintain the integrity of the data from a semantic standpoint, it fails to provide a simple interface for object retrieval – meaning that you must write a lot of code to properly repopulate and reconstruct objects from XML files.

The most common solution is to use a combination of the file system and a relational database, especially in cases where binary data storage is a necessity. Performance is only a small consideration in this situation, but more often than not storing information in a database will yield better performance than flat files, as the database already provides an optimized data retrieval layer. Again there is also a measure of personal preference involved, and some people may find working with flat files simpler than working with a database or vice versa. Ultimately, it is important that as a developer you use the best approach possible for the job and mold personal preference around what produces the best results.

{mospagebreak title=Error Handling}

Error handling is the next subject for discussion. Error handling is a topic that could (and does) fill an entire book, but for our purposes we only need to understand the fundamentals. Error handling should be “graceful” in any system – meaning that the application should know how to detect its own errors and handle them in some appropriate manner with minimal interruption for end users. In PHP, error handling is something that developers tend to overcomplicate. There is a tendency to use elaborate error handling classes that store far more information than is needed and not the information that is really needed. The following example demonstrates a simple error class.

<?php
class Error
{
 var $number;
 var $string;
 var $file;
 var $line;

 function setError($number, $string, $file, $line)
 {
  $this->number = $number;
  $this->string = $string;
  $this->file = $file;
  $this->line = $line;

  $this->showError();
 }

function showError()
{
 print ‘Error (‘.$this->code.’): ‘.$this->message.”<br />
”.$this->file.’ (‘.$this->line.’)<br />’;
}
}
?>

This very simple class takes in the error data via the “setError” function and spits the data back out via “showError.” The two methods are chained together. While this class is not especially useful for handling errors gracefully, it is the foundation for capturing the errors after attempts at a graceful recovery have failed and for acting upon the information provided by the error. Task-specific error handling is best left for the actual scripts or classes performing the task.

Implementing this class as our error handler in PHP is simple. See the following example.

<?php
$errorHandler = &new Error();
set_error_handler(array(&$errorHandler, ‘setError’));
?>

This will send any error in the script to “$errorHandler”. Consider the following sample, where the script will attempt to recover from a failed database connection by iterating over a set of possible databases. Data from the configuration file that would be used in this situation is inserted into the file directly. Assume that “class.error.php” contains our error class and is available in the default include path.

<?php
$settings[‘db’][0][‘user’] = ‘someuser’;
$settings[‘db’][0][‘pass’] = ‘password’;
$settings[‘db’][0][‘host’] = ‘192.168.1.2’;
$settings[‘db’][0][‘name’] = ‘MyApp’;

$settings[‘db’][1][‘user’] = ‘root’;
$settings[‘db’][1][‘pass’] = ‘password’;
$settings[‘db’][1][‘host’] = ‘192.168.1.120’;
$settings[‘db’][1][‘name’] = ‘MyApp’;


$settings[‘db’][2][‘user’] = ‘root’;
$settings[‘db’][2][‘pass’] = ‘’;
$settings[‘db’][2][‘host’] = ‘localhost’;
$settings[‘db’][2][‘name’] = ‘MyApp’;

require_once(‘class.error.php’);
$errorHandler = &new Error();
set_error_handler(array(&$errorHandler, ‘setError’));

$dbh = false;
$try = 0;
do
{
 $dbh = @mysql_connect($settings[‘db’][$try][‘host’],
       $settings[‘db’][$try][‘user’],
        $settings[‘db’][$try][‘pass’]);
} while (($dbh === false) && ($try < count($settings[‘db’])));

if ($dbh === false)
{
 trigger_error(‘Unable to connect to database.’, E_USER_ERROR);
}
?>

In this example we have two available hosts that attempt to establish a database connection with “mysql_connect” one host at a time until a connection is established. This is a type of error recovery – our application is provided with alternate possible databases to try connecting to before failing. If no connection can be made, we will pass a fatal error notice to our error handler.

Typically just a warning would be generated from a failed MySQL connection, but since our example application is dependent on one, the script needs to halt. A more elaborate configuration could involve the error handler being configured with objects to delegate errors to, in this case perhaps some top level object that handles stopping a script and presenting a user with the appropriate message. We could also extend our Error class and delegate certain types of errors or errors that match certain keywords to the appropriate subclass.

In PHP 5, error handling is even more powerful with exceptions and try/catch structures, allowing developers to specialize error handling on a per-object or even per-task basis, maximizing the usefulness of the data provided by the exceptions. Since this article is meant to be general and the example above also works in PHP 5, we will not have an exceptions example, though the principles are still the same.

Error handling is a pretty broad subject and the example above only chips the iceberg. Ultimately, error handling becomes very narrow in the context of a particular task, and in those cases you as a developer must use your experience and your problem solving skills to determine what options are available for handling the error, and how to handle it as succinctly and effectively as possible.

One of the most important uses of error handling in a live application is to capture real errors generated by real use of the system. This allows developers to work with live data, often in ways they did not predict during initial testing. There is a problem, though; how do developers know what error data is going through their application? One word: logging.

{mospagebreak title=Event Logging}

Event logging is exactly what the name suggests – the recording of events in an application. Logging is most often associated with error logging and authentication logging, but can be used for a much wider array of problems. Logging can be used to track visitor statistics, form submissions, and database activities. Logging can even be used to catalog script execution step by step in a meaningful way that allows developers to review a detailed execution path with relevant data to isolate script problems. In most applications, error logging and authentication logging are the extent of what is needed, but be creative and use logs to your advantage when trying to solve more complex problems.

Storing our log files presents the same general decisions as choosing how to store our application data. I prefer logging to a database to simplify the collection of statistical data. There is a problem with that approach though – can you guess? What happens when our application cannot access the database used for logging?

This is where flat file logging comes in handy. When writing code to handle logging to a database, it is wise to always code your logging tool to switch over to a flat file when a database is not available. Consider the following extremely basic logging class.

<?php
class Log
{
 var $settings;
 var $dbh;
 var $method;

 function Log(&$settings, &$dbh)
 {
  $this->settings = &$settings;
  $this->dbh = &$dbh;
  $this->method = ‘db’;

  if (gettype($this->dbh) != ‘resource’)
  {
   $this->dbh = mysql_connect($settings[‘db’][‘host’],
                 $settings[‘db’][‘user’],
                 $settings[‘db’][‘pass’]);
  } 

  if (gettype($this->dbh) != ‘resource’)
  {
   $this->method = ‘file’;
  }
 } 

 function record($message)
{
 switch ($this->method)
 {
  case ‘file’ :
return $this->recordToFile($message);
   break;
  case ‘db’ :
   return $this->recordToDb($message);
   break;  
 }
}
}
?>

You can use your imagination for the “recordToFile” and “recordToDb” methods, as the intent is clear. Obviously the object would need to know the name of the file to which log messages should be appended; we can accomplish this by storing the path to the log file in settings or by telling the log explicitly where to write. We can also extend this class to log in different formats or even use subclassing to determine which logfiles are written to.

You can also use your imagination to tangle up the “Error” and “Log” classes discussed above in all sorts of ways. The following example shows a basic way to configure the error object with a logging object. The example will also demonstrate the use of multiple debugging levels to determine what information is logged. Assume all class and configuration code from above is available to this script. There are a couple of new methods on the error handler here, but the usage is obvious and I will not define the code for those methods.

<?php
define(‘DEBUG_LEVEL’, 1);

require_once(‘class.log.php’);
$log = &new Log(&$settings, NULL);

if (DEBUG_LEVEL > 0)
{
 $log->record(‘PID ‘.getmypid().’ Started’);
}

require_once(‘class.error.php’);
$errorHandler = &new Error();
$errorHandler->setLog(&$log);
set_error_handler(array(&$errorHandler, ‘setError’));

if (DEBUG_LEVEL > 0)
{
 $log->record(‘Trying database connection’);
}

$dbh = @mysql_connect($settings[‘db’][‘host’],
        $settings[‘db’][‘user’],
        $settings[‘db’][‘pass’]);

if (gettype($dbh) != ‘resource’)
{
 trigger_error(‘Unable to connect to database’);
}
elseif (DEBUG_LEVEL > 0)
{
 $log->record(‘Database connection established’);
}


if (DEBUG_LEVEL > 0)
{
 $log->record(‘PID ‘.getmypid().’ Ended’);
}

?>

In this example, setting “DEBUG_LEVEL” to 0 causes the script to only record catastrophic errors, whereas setting it to 1 will cause the script to record every action taken. In a situation where our task was performed by another object, we would want to be sure that the object knew as little about the error object as possible – basically the “setError” method. This allows us to pass any subclass of the error object to our class without changing code in the dependant class.

Like many of the practices discussed thus far, the most important thing about logging is that you use it. Logging is an invaluable tool for maintaining an application. It is the only method by which detailed application information can be captured transparently and stored for later review. This information can, when provided in sufficient detail and context, provide a conceptual window through which to view a system problem.

A quick note – you probably noticed that I did not choose to use a database abstraction layer in my example code. There are a few reasons for this, the first of which is brevity. I do not care to delve into which layer is the best and how they work. Next, most PHP applications do not need a database abstraction layer. It is not the norm for PHP applications to require portability to other databases, and even though the need does exist at times we should use classes judiciously and avoid them when they are not needed.

Finally, I want to preempt the common argument that an abstraction layer enforces uniform access methods throughout code. Anyone familiar with ADODB or PEAR::DB knows full well that these packages are a mess and allow you to interact with a database in a variety of methods, each inconsistent with the other in theory and implementation. While these layers have their usage, they do not belong in most Web applications. Adhering to conventions and writing clean code will solve for uniformity, with or without an abstraction layer.

Conclusion

This article has covered some important fundamentals for PHP application development. Building on what we discussed in part one, we examined data storage, system configuration, error handling and logging. Though the exploration of each was brief, the overall purpose of each was clear. Practicing the use of these concepts greatly simplifies application maintenance and testing and, if used properly, it can accelerate system debugging radically.

My implementations are not necessarily “the” way to practice these concepts, they are simply the way I have learned through experience and study to handle them. I encourage you with this, as with any new idea, to poke and prod at it and form your own opinions. The article is intentionally light on code to facilitate this. If you analyze the problem and form your own solutions using the contents of this article as a sort of guide, you will find a solution to most any problem these concepts can solve.

In the next article we will discuss some basic database planning considerations and naming conventions for databases and discuss user authentication and management. See you then!

[gp-comments width="770" linklove="off" ]

antalya escort bayan antalya escort bayan Antalya escort