*
PHP Code Example
Code is a grossly misleading term. It implies that programs must be incomprehensibly constructed by secret guilds of cryptographers, that the goal of programming is maximum obfuscation through inscrutable cleverness, and that the programmer's thaumaturgical powers are much like the old magicians “code” — explanations shall never be revealed.
The truth is: code must be readable. Over time, the major cost of code is not the initial (and too often short-sighted) development effort, it is the amount of time spent on maintenance—on making the code run faster, more appropriately, responsively adapting to the fickle demands of an ever-changing world. The expense of such maintenance is directly proportional to the trickiness of the code. Consequently, creating code that isn't coded
is (almost) always the wiser choice.
The following example is a short, object-oriented report that analyzes new account activity. It is a real-world example, necessitated by a barrage of illicit login attempts that threatened to take down a Drupal site. (And contributed to my decision to steadfastly avoid commercial CMS products—but that's another story.) The report runs a parameterized SQL query, then hands off the report formatting chores to a standardized view. It strives to be self-documenting and understandable. (Nonetheless, and possibly calling into question whether or not I have achieved my stated goal of writing easily comprehensible code, you'll find lots of explanatory notes interleaved below.)
COBOL, the first widely adopted compiled computer language, required only one thing: an IDENTIFICATION DIVISION. Whether or not one aspires to COBOL's longevity, all code should begin with a similar statement of name and goals. Consider it an homage to Grace Hopper (even if you haven't (yet) found self-identification indispensable).
<?php
/**********************************
* lookupRegistrationAttempts.php
*----------------------------
* Purpose: Report Recent Registration Attempts on Drupal site
*----------------------------
* 1.0.0 (cheth) 2015-Apr-16 initial implementation
* 1.0.1 (cheth) 2015-Apr-26 allow reporting for various time periods.
**********************************/
Most programs need to access a common set of procedures, schemas, assets, and related system resources. Having a standard entry point for all programs simplifies the establishment of this commonality. The require_once
verb is preferred over the more common include
, since it is unlikely the code will work if the standard config file can't be found. (There may be some merit to the argument that the filename config.php
is overly common and thereby poses a security risk.)
/**************************
* standard initialization *
**************************/
require_once('../config.php');
In many environments the config file's responsibilities include loading standard classes (in addition to defining some sort of INCLUDE_PATH ). Reporting is typically a specialized need, not handled by config.php. So we initialize it locally.
/*********************
* initialize classes *
*********************/
require_once(INCLUDE_PATH . 'models/MySQLReportModel.php');
$rep = new MySQLReporter;
Sanitizing user input is an absolute necessity. XSS exploits, buffer overflows, SQL injection, and similar vulnerabilities take down sites every day. These problems are relatively easy to guard against, yet all too often overlooked. In this case the gritty details of sanitization are handled externally, by the report class.
/***************************
* get runtime param values *
***************************/
$myValues = $rep->getParamValues(); // parse and sanitize POST vars
$myTimestamp = $myValues->start_date;
As well as giving a title to our programmer-seen code, it's also nice to give a title to the end-user product, the report.
/********************
* set report params *
********************/
$rep->setTitle("Registration Attempts");
A SQL query is the heart of this reporting program. An in-depth analysis of SQL query strategies is beyond the scope of this write-up; but I will point out a couple features.
All computed columns are explicitly given a column name. This is always good practice; for this particular report class it is even more important since the column names become the report column headings.
The colon preceding the :myTimeStamp reference on line 48 indicates that this is a parameterized query. The actual time stamp variable is sent to the SQL engine separately from the query to prevent SQL injections and provide additional reasonableness tests. In this case the reasonableness test requires that the variable be a string by specifying PDO::PARAM_STR on line 58.
/************
* SQL query *
************/
$sql = "
SELECT
COUNT(*) AS counter
,accesslog.hostname
,MIN(FROM_UNIXTIME(accesslog.timestamp)) AS earliest
,MAX(FROM_UNIXTIME(accesslog.timestamp)) AS latest
,users.name
FROM
accesslog
LEFT OUTER JOIN
users on users.id = accesslog.user_id
WHERE
timestamp >= :myTimestamp
GROUP BY
hostname
HAVING
registration_attempts > 0
ORDER BY
registration_attempts DESC
,counter DESC
";
$params[] = array(':myTimeStamp', $dashed_title, PDO::PARAM_STR, 'value');
Actually preparing the report is the final task. This is the View part of the Model-View-Controller design pattern. (The SQL statement is the Model, and this entire file could be considered the Controller—so, this time, MVC separations are not entirely implemented as discrete files.)
Note that there is no closing ?> tag. I used to always include this formal end statement, believing it good practice to signify that processing was complete. Unfortunately the PHP end tag does not mean stop processing—it simply means switch to HTML output mode. Any blank lines after the end tag get shoved to the browser. This is likely to kill AJAX JSON responders and file downloaders (and can cause other problems). I still like formal end-of-file (eof) statements; but I now embed them as a comment.
/******************
* generate report *
******************/
$rep->PrepareReport($sql, $params);
/* eof */
My view of what constitutes good code has progressed a lot over the years. But I still remember the lesson I learned from my very first program. I was coding in FORTRAN then (OMG). Believing I was being smart and efficient I had squeezed three or four separate statements onto one 80 character line. No matter how hard I tried I couldn't get that program to work. Thankfully, one day someone explained to me that it wasn't helpful to shove as much as possible into each line. For me that was the moment when I separated from the herd and became a programmer. Separation made things so much easier. Separation of statements once crowded into a single line, separation into functions and classes, separation into a Model-View-Controller pattern—each increasing level of separation has improved my programming. Separation: my entrant for an overarching holistic programming paradigm.
And with that it is time to bid you, dear reader, a joyous separation.