Three lessons from amateur PHP code

Hacks cans
Photo CC-BY-SA by tnarik

I did not get a CS degree and thus learned web development as a self-taught amateur. While that was fun, it also meant that in the process of building things without a curriculum or a mentor to provide a foundation I wrote pretty bad code in the beginning.

I have dug up the PHP code for a site which has been superseded with a Drupal site since 2009 and it's a great way to show failure by example without anyone getting hurt.

The following shows common mistakes made by amateur PHP programmers and a few pointers on how to fix them and become a better developer.

Mistake #1: Repeating yourself

Every time you see redundant code it should give you pause. Even a decade ago I recognized this and tried to move the page header into a separate file. Here is the start of item.php and dozens of other files like it:

<?php
$pagetitle="Item Details​";
include "main.php";
echo "<div id=\"content\">";
// [...]

Viewing an item would thus be the URL /item.php?id=123. This structure exemplifies that the author has not thought enough about inheritance and overall structure. Furthermore, we can see in main.php that the file is just dumping content to the output like the all other PHP files in that application:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<?php
include "settings.php";

It's clear that the author has not even fully internalized procedural programming and is using includes simply to reduce redundancy in a static, linear script.

Fixing the mistakes

Point all requests to index.php as the sole entry point and use parameters to call the respective logic. Not only will you reduce errors but you can also centralize parameter checking and sanitization, as well as authentication and authorization. You can also easily add nice, readable URLs with a one-liner in htaccess.

Convert the static pages such as item.php into functions. Which is basically a given, if you restructure everything around your index.php as recommended above. In this case if we had all item.php logic wrapped in a function (or more likely a collection of them), we could call it where required through our routing logic and not attempt to build a linear script for each page. Ideally, you would follow established patterns like MVC for your program structure when doing so.

Don't solve this yourself. Seriously consider if your homebrew application should continue to exist. The above points explain how one would fix something like it if you cannot ditch it but consider carefully if a framework such as Symfony2 or a CMS like Drupal is not the better solution to start from scratch, since they provide all that essential work of handling I/O, routing pages, abstracting templating, etc. for you, if you invest the time to learn how these systems work.

Mistake #2: Mixing logic, data and display

Here is an additional excerpt from the item.php shown above:

if ($row[isbn] != NULL)
echo "<tr><td>ISBN/ISSN #:</td><td> $row[isbn] </td></tr>";
echo "<tr><td>Section:</td><td> $row[section]</td></tr>";
// [...] 20 more <tr> like that
$commentquery = mysql_query("SELECT comment FROM comments WHERE item='$row[item_id]'");
if (!mysql_num_rows($commentquery)==0)
echo "<br /><br />Comments by members about this item:";

while($row = mysql_fetch_array( $commentquery )) {
    echo "<div id=\"comdiv\">";
    echo nl2br($row['comment']);
    echo "</div>";
}

Thankfully, the query parameter was properly sanitized, so it is not a case of little Bobby Tables but it is a Smörgåsbord of bad patterns. Not only is redundancy a concern but the indiscriminate mix of data, html markup and query logic shows missing levels of abstraction. The weird string double escaping we'll just ignore.

Fixing the mistakes

Use PDO and prepared statements. If you write your SQL by hand and work with user input, you are asking for trouble. PDO tutorials abound.

Functions, functions, functions. The author should have seen the redundancy in his or her code and concluded that a helper function should deal with creating the table (let's ignore whether a table is the right solution here). The block on comments could equally as well be encapsulated in a function. However, that function alone would not add much readability (at least as long as it is as short as it is now), unless one takes out the markup, which the following recommendation encompasses.

Create templates. Even if you do not follow the general advice from mistake #1 on using an established base to build upon, you can improve legacy code by stripping out the markup into templates. Even if you don't want to fully commit to Symfony, you could use their Twig engine to do so and achieve much more readable and maintainable code. Again, consider not reinventing the wheel.

Mistake #3: Sloppy code conventions

if (!mysql_num_rows($commentquery)==0)
echo "<br /><br />Comments by members about this item:";

The line above we already saw in mistake #2. The problem here is that through missing brackets, spaces, and indentations the code becomes significantly harder to read. It's easy to overlook the scope of the if-loop and for example incorrectly assume that the while-loop following it is still in it's scope. The many formatting problems in the code were mainly due to the fact that I didn't use software to help me stick to a standard, since I wrote it all by hand.

Fixing the mistakes

Use an IDE. A text editor with syntax highlighting is just not enough, it's too easy to make mistakes. Your environment, whichever you decide upon, should do auto-formatting and code checking as-you-type. I use PHPStorm but the same can be achieved with free tools such as Eclipse. 

Follow PSR-1/2. If you have until now followed your personal preferences, just get over yourself and standardize to PSR-2, that way all your code and all modern libraries look the same. There are also tools to help you convert your existing code

All of the above will not make you into a great developer but I hope to have provided helpful advice to others that I wish I had been given at the time.

Goodbye comments

various spam containers
Photo CC-BY by arndog

Today I removed this blog's comment functionality after having it for over seven years. I do not host debates and do not have content which regularly inspires comments apart from "thanks" so I don't think that this will in any way diminish this blog. I'm sure if someone actually needs help or has information they want to give me, they will find the about page. 

And on the other hand, comment spam is simply out of control. At first I had Drupal's image captcha which worked well for a while but had to be exchanged for Recaptcha but when that let too many spam posts through I switched to Mollom and they have provided an excellent service but still let about one false negative through every other day for the 500+ attempts every day. 

So, to follow the spirit of @AvoidComments, no more more comments here.

Using a Kindle as a monitoring tool (or not)

I had an old Kindle 3 lying around and really wanted to do something fun with the e-ink display in it, especially since its Linux underneath anyway.

If you browse around the web you'll find plenty of information to get you started with the prerequisite steps of rooting the device, enabling network and ssh. Since a lot of the information is hidden in forums (ugh), I'll point to Liraz Siri's tutorial which covers all important points.

Developing a kindlet

Unless you want to directly work on the framebuffer you will want to create a java application which the Kindle can run, called a kindlet. I strongly suggest you follow David Given's guide. Getting his Hello World to run consistently is a good starting point for everyone like me who only dabbles with Java. Additionally, the MobileRead wiki has additional information.

Since developing a kindlet without any resources from Amazon basically amounts to compiling, copying, opening and repeating that process it's essential to automate that process, thus I recommend ammending Given's excellent makekindlet script with the following  two lines (for OS X):

[...]
jarsigner -keystore $KEYSTORE -storepass password $JAR dn$USER

cp $JAR /Volumes/Kindle/documents/
diskutil eject /Volumes/Kindle 

Network communication in a Kindlet

To do anything useful in a Kindlet apparently requires one to have the ability to work with sockets, so the security settings need to be adjusted. A blog post by Alex Hutter explains how, i.e. add the following in external.policy:

grant signedBy "Kindlet" {
 permission java.net.SocketPermission "*:443-", "accept,connect,listen,resolve";
 permission java.net.SocketPermission "*:80-", "accept,connect,listen,resolve";

If you are stuck at such a point it's often helpful to visit the crash.log in developer/APPNAME/work to point you in the right direction of the problem.

Rendering content

Now, the rest should be easy, right? Well, rendering local images worked fine with this example snippet, however any permutation of loading network resources into images went exactly nowhere, getWidth() returns 0 and no image can be rendered and that's where I'm currently stuck and discontinuing this project.

Summary

The Kindle is NOT intended as a general use computing device. 

The quote above is from Hutter's blog post, I don't have much to add to that. This was a fun project but it became clear that without getting to know Java a lot better this would not be worth the effort, compared to solving the problem with an Android app, no matter how nice the display is.

Track when you lock your desktop

a stopwatch
Photo CC-BY by Jerry

If you do any sort of work where you have to report your activities in 15-minute increments, you have probably experienced that it is very easy to miss things, especially if you often have to deal with interruptions.

There are numerous solutions to parts of this problem such as apps which continuously poke you to update a tracker, to ones which simply log everything and sum it up. I use some of them but I also add another check to my routine to have more accurate timing information when I wasn't paying attention. What I'm doing is writing timestamps to a file which register screen locks/unlocks as well as suspend and it has in the past year helped me to significantly become more accurate in my tracking by having reference timestamps such as the following excerpt:

Thu Jan 23 09:53:48 CET 2014: SCREEN_UNLOCKED
Thu Jan 23 10:02:21 CET 2014: SCREEN_LOCKED
Thu Jan 23 10:18:16 CET 2014: SCREEN_UNLOCKED
Thu Jan 23 10:27:02 CET 2014: SCREEN_LOCKED

If you want to do this, too, just follow one of the examples below. If you want to do something similar and customize it, the problem is rather trivial if you break it down into a trigger and an action. The first is heavily dependent on your environment, the latter could be anything.

Linux/GNOME

To react to events you'll have to run a command at login which then continuously monitors DBUS to see if the relevant event has fired. Here is a working example:

$ #!/bin/bash

function write_log {
  if [ -z $1 ]; then
    1="unspecified"
  fi
  echo -e "$1\t$(date)" >> "/home/youruser/time.log"
}

write_log "LOGGED_IN"

# Monitor gnome for screen locking. Log these events.
dbus-monitor --session "type='signal',interface='org.gnome.ScreenSaver'" | \
  (
    while true; do
      read X;
      if echo $X | grep "boolean true" &> /dev/null; then
        write_log "SCREEN_LOCKED"
      elif echo $X | grep "boolean false" &> /dev/null; then
        write_log "SCREEN_UNLOCKED"
      fi
    done
  )

OS X

The only decent trigger method I found (albeit after only 15 minutes of googling) is the paid app Scenario. The app itself should be self-explanatory and allows you to put one or several scripts into the respective folders for sleep, lock, log out, etc. Since I'm much more familiar with shell scripting than Applescript, I'm just using the later to call the former. Obviously, this could be simplified.

Applescript in Scenario:

do shell script "~/.bin/screenlock.sh unlock"

Shell script:

#!/bin/bash

if [ $1 = "lock" ]
then
  echo -e "`date`: SCREEN_LOCKED" >> ~/time.log
elif [ "$1" = "unlock" ]
then
  echo -e "`date`: SCREEN_UNLOCKED" >> ~/time.log
else
  echo -e "`date`: UNKNOWN_ACTION" >> ~/time.log
fi

 

Have a better way to do this? Let me know in the comments.

Running Drupal on a proper PHP version with IUS

I recently read Thijs Feryn post on the maturity of PHP and he makes a convincing argument for actually using what PHP provides with 5.5 now being stable.

Drupal 7 has seen many improvements over the past year in regards to 5.4 compatibility and as it turns out, it now runs pretty smoothly, though many modules still create E_NOTICEs due to legacy code. The breakage which 5.4 caused in many places when Drupal 7 (and let's not even talk about a 6 site) came out is not comparable with upgrading from 5.4 to 5.5, which is mostly harmless.

Managing PHP versions

You have basically two options for upgrading PHP on your server: find some pre-compiled packages or build from source. The latter should be avoided by nearly everyone, unless you're willing to diligently subscribe to a release mailing list and compile again and again.

On CentOS/RHEL, which is my preferred hosting platform at the moment one can rely on the excellent IUS repository. It makes switching between 5.3, 5.4 and 5.5 extremely easy. Once you have IUS installed, you can select your packages via the name php5Xu. Which means you can switch between 5.3 and 5.5 simply by removing the packages installed from php53u and installing again with php55u. All I needed afterwards to get PHP running again was copying the /etc/php-fpm.d/www. conf.rpmsave over www. conf and so 15 minutes later this blog is running on 5.5:


# yum list installed | grep php
php55u-cli.x86_64 5.5.5-2.ius.centos6 @ius
php55u-common.x86_64 5.5.5-2.ius.centos6 @ius
php55u-fpm.x86_64 5.5.5-2.ius.centos6 @ius
php55u-gd.x86_64 5.5.5-2.ius.centos6 @ius
...

An additional benefit is that IUS provides packages in sync with the latest released package. This is very helpful in determining what you are actually running. For example, your server might report that you are running 5.3.3 (as stock CentOS 6.4 does), even if critical fixes have been backported by RedHat, Fedora, et al from later versions, with IUS you'll get a proper 5.3.27.

Caveat: Of course, don't try this on production if you don't know what you're doing.

Sterling's Dark Euphoria

Photo CC BY/SA by Matt Biddulph

Bruce Sterling has struck again. At the Webstock '13 conference he gave a talk (as he is prone to do) in which he looked at the web and society in 2013 and produced a fascinating narrative around it.

Stacks

The first important point I took away from his talk is how critically divergent the global online companies we depend on have become from traditional 20th century corporations as well as from the open web itself. He calls these vertically-integrated, global organizations stacks and identifies key factors in their difference to the web, such as a a proprietary operating system and a post-Internet, non-jailbrakeable stack-device (tablets, phones, e-books, et al) and much much more. How does he put it?

The Internet had users, stacks have livestock.

Interestingly, the stacks are highly unstable, he says, and he believes they shouldn't outlive the Arpanet in the long-run, proves it by cat, and you feel a kind of dark euphoria for what's to come.

In sum, just go watch his talk, I couldn't fit this into 140 characters.

Webstock '13: Bruce Sterling - What a feeling! from Webstock on Vimeo.

Pages

Subscribe to Front page feed