1. Introduction
PHP (http://www.php.net) is a powerful server side web scripting solution. It has quickly grown in popularity and according to the 2000 January Netcraft Web Server Survey PHP is installed on 12.8% of all web sites. Much of its syntax is borrowed from C, Java and Perl with a couple of unique PHP-specific features thrown in. The goal of the language is to allow web developers to write dynamically generated pages quickly.
Being a good PHP hacker isn't just about writing single line solutions to complex problems. For example, web gurus know that speed of coding is much more important than speed of code. In this article we'll look at techniques that can help you become a better PHP hacker. We'll assume that you have a basic knowledge of PHP and databases.
If nothing else, you should leave here with the 3 key ideals for PHP hackers:
- Laziness is a Virtue
- Chameleon Coding
- Speed of Coding, Not Speed of Code
2. Laziness is a Virtue
2.1. Introduction
It seems strange to think of a web programmer as lazy. Most of us work one hundred-hour week's in our quest to join the gold rush. In fact, we need to be lazy because we are so busy.
There are two key ways to be lazy. Firstly always use existing code when it is available, just integrate it into your standards and project. The second technique is to develop a library of helpful functions that let you be lazy in the future.
2.2. Use Other People's Code
We need to use laziness to our advantage and PHP is the perfect tool. PHP
was born and raised in an open source environment. The community holds open
source ideals close to its heart. As a result there are thousands of people
on the mailing list willing to share their knowledge and code. There are
also many open source PHP projects that you can tap into. I'm not suggesting
that you spend all day asking people to write code for you. But through
clever use of the knowledge base, mailing list archives and PHP projects
you can save yourself a lot of time.
PHP Knowledge Base - http://php.faqts.com
PHP Mailing List Archive - http://www.progressive-comp.com/Lists/?l=php3-general&r=1&w=2
2.3. Helpful Functions and Classes
2.3.1. Introduction
In this section we will work at developing a library of PHP code which will aid us in future development. A small amount of work now let's us be lazy in the future.
Some of this code has been taken from open source PHP projects. Other parts from the mailing list archives. In fact, all the work I really needed to do was structure the code into a coherent library of functions.
2.3.2. Database Abstraction
One of the features / problems with PHP is that it does not have a uniform method for accessing databases. There are specialized functions for each database PHP is able to connect to. This is a feature because it allows you to optimize your database code. It is a problem because it makes your code less portable and increases the learning curve for newcomers.
A number of database wrapper classes have been developed to solve this problem. They provide a uniform set of functions for accessing any database. Personally I like them because I find it much easier to remember a few simple functions like query and next_record than having to think about database handles, connections and so on.
The most commonly used (and defacto standard) is PHPLib - http://phplib.netuse.de/
Metabase - http://phpclasses.UpperDesign.com/browse.html/package/20
There is also PHPDB - http://phpdb.linuxbox.com/
2.3.3. Session Management
The main purpose of PHPLib is session management. This allows you to associate data with visitors to your site for the duration of their stay. This can be useful for remembering options and so on.
PHP4 has session management features built into the PHP internal function library.
2.3.4. Debugging Variables
There is limited debugging support for PHP. This is no Smalltalk environment where you can browse objects and perform methods on them. Instead we need to make creative use of the old, reliable echo statement.
The first thing we need to be able to do is look at the value of variables. The loose typing of PHP lets us use most variables directly in strings. This is great for numbers and so on, but falls down when we are dealing with arrays and objects.
The other problem with debugging is that sometimes I'm not even sure what a variable is likely to contain. If I was, there be no need to debug.
So, lets be smart now and lazy for the rest of time. We can write a function that shows us the type and value of any variable.
function ss_array_as_string (&$array, $column = 0) { $str = "Array(<BR>\n"; while(list($var, $val) = each($array)){ for ($i = 0; $i < $column+1; $i++){ $str .= " "; } $str .= $var.' ==> '; $str .= ss_as_string($val, $column+1)."<BR>\n"; } for ($i = 0; $i < $column; $i++){ $str .= " "; } return $str.')'; } function ss_object_as_string (&$object, $column = 0) { if (empty($object->classname)) { return "$object"; } else { $str = $object->classname."(<BR>\n"; while (list(,$var) = each($object->persistent_slots)) { for ($i = 0; $i < $column; $i++){ $str .= " "; } global $$var; $str .= $var.' ==> '; $str .= ss_as_string($$var, column+1)."<BR>\n"; } for ($i = 0; $i < $column; $i++){ $str .= " "; } return $str.')'; } } function ss_as_string (&$thing, $column = 0) { if (is_object($thing)) { return ss_object_as_string($thing, $column); } elseif (is_array($thing)) { return ss_array_as_string($thing, $column); } elseif (is_double($thing)) { return "Double(".$thing.")"; } elseif (is_long($thing)) { return "Long(".$thing.")"; } elseif (is_string($thing)) { return "String(".$thing.")"; } else { return "Unknown(".$thing.")"; } }Note that these functions work together to correctly print, format and indent arrays. They are also able to print objects when they have been defined with the PHPLIB standard variables classname (the name of the class) and persistent_slots (an array of the variable names we care about).
Now we can see the state of any variable by just doing:
echo ss_as_string($my_variable);We can see the value of all variables currently defined in the PHP namespace with:
echo ss_as_string($GLOBALS);
2.3.5. Log Functions
A great way to debug is through logging. It's even easier if you can leave the log messages through your code and turn them on and off with a single command. To facilitate this we will create a number of logging functions.
$ss_log_level = 0; $ss_log_filename = '/tmp/ss-log'; $ss_log_levels = array( NONE => 0, ERROR => 1, INFO => 2, DEBUG => 3); function ss_log_set_level ($level = ERROR) { global $ss_log_level; $ss_log_level = $level; } function ss_log ($level, $message) { global $ss_log_level, $ss_log_filename; if ($ss_log_levels[$ss_log_level] < $ss_log_levels[$level]) { // no logging to be done return false; } $fd = fopen($ss_log_filename, "a+"); fputs($fd, $level.' - ['.ss_timestamp_pretty().'] - '.$message."\n"); fclose($fd); return true; } function ss_log_reset () { global $ss_log_filename; @unlink($ss_log_filename); }
There are 4 logging levels available. Log messages will only be displayed if they are at a level less verbose than that currently set. So, we can turn on logging with the following command:
ss_log_set_level(INFO);Now any log messages from the levels ERROR or INFO will be recorded. DEBUG messages will be ignored. We can have as many log entries as we like. They take the form:
ss_log(ERROR, "testing level ERROR"); ss_log(INFO, "testing level INFO"); ss_log(DEBUG, "testing level DEBUG");This will add the following entries to the log:
ERROR - [Feb 10, 2000 20:58:17] - testing level ERROR INFO - [Feb 10, 2000 20:58:17] - testing level INFOYou can empty the log at any time with:
ss_log_reset();
2.3.6. Optimization
We need a way to test the execution speed of our code before we can easily perform optimizations. A set of timing functions that utilize microtime() is the easiest method:
function ss_timing_start ($name = 'default') { global $ss_timing_start_times; $ss_timing_start_times[$name] = explode(' ', microtime()); } function ss_timing_stop ($name = 'default') { global $ss_timing_stop_times; $ss_timing_stop_times[$name] = explode(' ', microtime()); } function ss_timing_current ($name = 'default') { global $ss_timing_start_times, $ss_timing_stop_times; if (!isset($ss_timing_start_times[$name])) { return 0; } if (!isset($ss_timing_stop_times[$name])) { $stop_time = explode(' ', microtime()); } else { $stop_time = $ss_timing_stop_times[$name]; } // do the big numbers first so the small ones aren't lost $current = $stop_time[1] - $ss_timing_start_times[$name][1]; $current += $stop_time[0] - $ss_timing_start_times[$name][0]; return $current; }Now we can check the execution time of any code very easily. We can even run a number of execution time checks simultaneously because we have established named timers.
See the optimizations section below for the examination of echo versus inline coding for an example of the use of these functions.
2.3.7. Debugging and Optimizing Database Operations
The best way to gauge the stress you are placing on the database with your pages is through observation. We will combine the logging and timing code above to assist us in this process.
We will alter the query() function in PHPLib, adding debugging and optimizing capabilities that we can enable and disable easily.
function query($Query_String, $halt_on_error = 1) { $this->connect(); ss_timing_start(); $this->Query_ID = @mysql_query($Query_String,$this->Link_ID); ss_timing_stop(); ss_log(INFO, ss_timing_current().' Secs - '.$Query_String); $this->Row = 0; $this->Errno = mysql_errno(); $this->Error = mysql_error(); if ($halt_on_error && !$this->Query_ID) { $this->halt("Invalid SQL: ".$Query_String); } return $this->Query_ID; }
3. Chameleon Coding
3.1. Introduction
A chameleon is a lizard that is well known for its ability to change skin
color. This is a useful metaphor for web programming as it highlights the
importance of separating well structured and stable backend code from the
dynamic web pages it supports.
PHP is the perfect language for chameleon coding as it supports both structured classes and simple web scripting.
3.2. Structuring your PHP Code
3.2.1. Introduction
When writing PHP code we need to make a clear distinction between the code which does the principal work of the application and the code which is used to display that work to the user.
The backend code does the difficult tasks like talking to the database, logging, and performing calculations.
The pages that display the interface to these operations are part of the front end.
3.2.2. Dynamic, Hackable Frontend Code
Mixing programming code in with HTML is messy. We can talk about ways to format the code or structure your pages, but the end result will still be quite complicated.
We need to move as much of the code away from the HTML as possible. But, we need to do this so that we don't get lost in the interaction between our application and the user interface.
A web site is a dynamic target. It is continually evolving, improving and changing. We need to keep our HTML pages simple so that these changes can be made quickly and easily. The best way to do that is by making all calls to PHP code simple and their results obvious.
We shouldn't worry too much about the structure of the PHP code contained in the front end, it will change soon anyway.
That means that we need to remove all structured code from the actual pages into the supporting include files. All common operations should be encapsulated into functions contained in the backend.
3.2.3. Stable, Structured Backend Code In complete contrast to the web pages your backend code should be well designed, documented and structured. All the time you invest here is well spent, next time you need a page quickly hacked together all the hard parts will be already done waiting for you in backend functions.
Your backend code should be arranged into a set of include files. These should be either included dynamically when required, or automatically included in all pages through the use of the php3_auto_prepend_file directive.
If you need to include HTML in your backend code it should be as generic as possible. All presentation and layout should really be contained in the front end code. Exceptions to this rule are obvious when they arise, for example, the creation of select boxes for a date selection form.
PHP is flexible enough to let you design your code using classes and or functions. My object oriented background means that I like to create a class to represent each facet of the application. All database queries are encapsulated in these classes, hidden from the front end pages completely. This helps by keeping all database code in a single location and simplifying the PHP code contained in pages.