Legacy PHP Application Tour & Upgrade

The process of migrating my applications from PHP 5.6 -> 7.3 has been a long one – mainly due to limited time to work on it, and additional tasks being added to the migration list. But I’ve finally made it through the low hanging fruit of easy client sites and am now migrating the first project with a significant amount of back end PHP code. In this case, it’s a legacy CMS I wrote 14+ years ago called LampLight Content Manager.

I wrote LampLight back when WordPress was still a bit complex for some of my customers, and it was also a time of frequent security issues and hacked sites bogging down the WP ecosystem. Today it’s a much different beast and my new projects which need a CMS default to WordPress.

Anyway check out this beauty:

LampLight Content Manager. Circa 2005.

The buttons along the top represent toggle-able modules such as Articles, Gallery, Polls, etc, that I could enable or disable based on the client’s needs. I had HTML pages ready to drop into the website that could load static articles or a list of them. The editor bar inserts BBCodes because this pre-dates robust rich text editors that I liked enough. Something that less tech-savvy customers could use without creating a nested html mess and didn’t generate garbage markup.

All in all LampLight has served me well. A couple clients are still using it, and my own company website is still eating this dogfood so to speak. It’s held up well over time, and even received a dashboard design refresh along with a theme switcher several years ago. But LampLight was built for PHP 5.3 and then migrated ages ago to 5.6. And there it sat until today. Let’s have a look deeper and go through my migration process! We’ll clean it up, get it working locally for the first time and migrate to PHP 7.3. (To clarify, this is still a discontinued project, but I need to keep it working for my clients still using it.) Oddly I’m still a bit proud of this piece of software. Nothing else I’ve written has been so useful, stable, or generated so many sales. I’ll be sad to see it go.

Code Overview

The code is a mix of object oriented and procedural. There’s no auto-loading just a few includes. Configuration is pretty organized and there are feature toggles for modules. The front end uses an earlier version of my TemplateSeed PHP view library. Markup is mostly XHTML and styles use CSS, though at the time I didn’t know how to configure paths properly in CSS image URLs so the CSS file is PHP. Yikes.

I use function libraries and custom plugins to reuse code. Even today, adding features is quite simple because everything is vanilla PHP.

It uses a MySQL database for storage and I have a db class which wraps the mysqli functions for at least a small layer of abstraction. Database migrations are non-existent, just a big SQL file to initialize the database. I write my queries in raw SQL but all parameters are properly escaped with built in SQL escape functions. This is how we did it back then kids!

Deployments are just a copy of the files via SFTP to a staging server for testing and review by the client. Then SFTP again to live. SQL changes were versioned so when I promoted a new version I would run the SQL alter table queries for that version. I can’t recall this ever biting me… I was veeeery careful.

I have never had this application working on my local development machine. I would FTP changes to a hidden sub-directory and use that to preview my changes. This meant I got really good at writing a lot of code before having to actually see what it does. I’ve struggled with what I think is my ISP throttling FTP traffic and in the evenings it’s super slow, so I do a lot of writing code without many previews. The good news though, is that part of the process this week is getting it working locally – yay for instant results!

Now, enough of a tour, let’s get to work!

Step 1: Run It Locally

I created a new private Git repo and copied all the site files into it from their old home in Mercurial at BitBucket. I didn’t bother migrating the history this time because I’m breaking it out of a repo that contains several small personal sites, and re-organizing things so decided to go for a fresh start.

Moved all the actual public site files into a public/ directory, cd in there and run it via the built in PHP webserver. And voila!! A ton of errors!

Working my way through, I made several small fixes to get at least the homepage and a cursory click through to be error free:

  • Create a local MySQL database and set up the tables.
  • Create copies of my config files – rename them config-PRODUCTION.php and edit the originals for localhost.
  • Replace absolute paths in the configs with something along the lines of: define("DOC_ROOT", dirname(__FILE__)."/");
  • Replace the deprecated __autoload() function with spl_autoload_register().

Step 2: Pretty URLs without Mod_Rewrite

The first real issue, was that using the built in PHP webserver means that my .htaccess file is not used and therefore my mod_rewrite rules to handle pretty URLs are not executed. On an Apache webserver this is used (.htaccess):

RewriteEngine On

# Test that the URL isn't pointing to a real dir or file:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

# It's a pretty url, so rewrite index.php/URL to the url get param:
RewriteRule ^(.*)$ index.php?url=$1 [PT,L]

Using the built in PHP webserver means that the url GET parameter is never set. So my bootstraping step now needs to check if it’s empty and try to use the PATH_INFO server variable:

// Grab our passed URL parameters.
$url = $_GET['url'] ?? $_SERVER['PATH_INFO'] ?? "";
$url = ltrim($url, '/');

$urlArray = array();
$urlArray = explode("/", trim($url) );

A few more small adjustments and it works! It’s running on localhost, pulling data from the local database, and I can even log into LampLight.

But I’m running PHP 7.3 locally! Why isn’t it blowing up?

Step 3: PHP 7 Errors

Other than the __autoload() problem, the only other real issue is a few uses of count() on null or non countable input. These were trivial to fix mainly by checking for null first. And that was mainly it! Apparently this project isn’t too fancy and it sticks to very core language features.

That isn’t to say that I’m done though. The code is still a mess, and hasn’t been tested beyond a cursory click through. And sadly no, I don’t have a test-suite for this project. So in the next post, I’ll get into the meat of this process by using some code analysis tools to clean things up and identify errors.

This should be fun, see you next time!

Leave a Reply

Your email address will not be published. Required fields are marked *