PHP, APIs, and the Woes of Taking Over a Codebase

At my job I’m currently the de facto software developer. A couple months ago, the previous software developer graduated, and while I already had some experience with our internal tools and built some of my own, I was now tasked with maintaining and building upon these tools.

I was immediately faced with a few challenges:

  1. Undocumented codebases
  2. Repetition of code across services
  3. No room for expansion
  4. Beauracratic restrictions

I’ve always loved PHP because of how powerful it is and how quickly you can spin up a server. I never really understood the hate that it gets, but when I started to look at the code for our internal systems, I started to get it.

With good structure and practices, PHP is a great tool, but it can easily get messy and convoluted. You shouldn’t be using PHP echo statements to write static CSS and HTML, yet there it was. Indescript variable names, unused if statements, etc. etc.

My first task was to duplicate one of our existing tools so another department could use it. I had initially planned to simply go in, clean up and document the code, and make some minor changes to add the new department to our server. However, after seeing the structure, I decided that I needed to start from scratch, and I was going to do it right this time.

Yet, I still had to deal with the afformentioned challenges. The code wasn’t built with growth in mind, some tasks in the codebase had already been solved and implemented in other internal tools but were solved again here, the inner workings of the code were undocumented and unclear, and I was limited to a single MySQL database and PHP server.

Our server administration team was already hesitant to having us write custom tools due to these exact problems that come from a constant cycle of student developers coming and leaving without documentation and plans for maintainence, so I knew I had to handle this carefully and plan for the future.

I laid out some steps for how I would accomplish this:

  1. Build the new department’s version from scratch
  2. Build an API that the new version runs on top of
  3. Migrate our current version to the new API

Simple enough.

I start by designing a UI that looks similar to the old version but runs on Bootstrap instead of custom CSS. I’m not a big fan of Bootstrap, largely because I think every Bootstrap site looks the same, and I’m trying my best not to add unnecessary dependencies, but Bootstrap is so well documented and supported that I tend to use it where I can.

I move on to rewriting the backend. I try to reuse the existing logic but find that the entire codebase is vulnerable to SQL injection. Some logic is overly complicated and some is so simple that it could lead to accidental record deletion (why store a unique index if you’re not going to use it?). The table structure isn’t ideal and the pseudo-API returns HTML responses that are loaded directly into the site, how lovely.

Some time later I have a duplicated version of the tool that looks the same on the surface but has a complete overhaul under the hood. Next up, building an API.

If I could have written the API in NodeJS and used Express as the router, I would have, but our existing systems were in PHP, and PHP is all I was allowed to use. So I start looking for how to build a good API in PHP but I’m having no luck. It looks like I can either build my own API solution entirely from scratch and hope that I’m doing things correctly, or use some bloated library that doesn’t support the latest versions of PHP.

Then I stumbled upon marcj/php-rest-service. Looking back, it feels like a moment of glory when I found this package, but in reality, I was very skeptical and quite disappointed. The package is now archived, hasn’t received an update in about 10 years, and the documentation is sparse to say the least.

However, it offered many of the features I was looking for. Complete RESTful support, request validation, single page routing, and zero dependencies. I wasn’t exactly comfortable using this package in our production environment, but I started playing around with it. While many features were undocumented, I found that there were some pretty cool hidden ones.

I started building my API on top of this package, but not without writing extensive documentation on how to use it, as well as adding some much needed bug fixes and PHP 7+ support.

At this point I had finished the API, and integrated it with both the new and old versions of the tool, when I started planning for future expansion. One thing that concerned me was that my custom changes to the package were not documented. If someone came along and made a new instance of the server or needed to reinstall the package from composer, the changes would be lost and the tool would be broken, so I decided it was time for a fork.

And like that, cdgco/php-rest-service was born.

In addition to bug fixes, PHP 7+ support, and a lengthy documentation with examples, I also added PUT body parsing, custom error code support, single file installation, plain text response support, custom response formatting, and automatic OpenAPI specification generation.

On the dev side, documentation is automatically generated through phpDocumentor and Docsify, testing is automated through CircleCI and releases are automatically triggered through GitHub actions.

Whether or not I’ll continue adding features to this package is a question that’s still up in the air, but I feel like I’ve reached a point where I can call it complete, feel comfortable using it in our production environments, and know that the package and APIs built with it will be supported, well documented and easy to maintain well into the future.

For more information on PHP REST Service, check out the website, and keep your eye out for another blog post specifically on the features of PHP REST Service in the near future.