Learning By Joking: A dockerized PHP FizzBuzz API
It’s no secret that we learn best by doing something: we remember vocabulary by repeating them, or better use them in sentences. We study math by solving problems. We hone our writing skills by writing. More often than not, those don’t have a well figured out point. Sometimes, though, we create a dockerized fizzbuzz web API.
Fizzbuzz is a surprisingly infamous children’s game: one after another the children count by saying out the number aloud. But if the number is a multiple of three, they have to say “fizz” instead. For multiples of five, it is “buzz.” If the number is a multiple of five and three, the child has to say “fizzbuzz.”
The game gained infamy with computer scientist and programmer applicants though! How so? As a simple challenge, i.e., write a loop that prints out the numbers up to 20 but print it with the rules of fizzbuzz, it is used as a screening for job interviews. The insight it provides is: has the applicant lied about their basic programming knowledge?
Thanks to this popularity fizzbuzz is often used as a starting problem on programming challenge platforms. Or as a starting point, similar to hello-world. But it is inefficient and boring to copy paste or rewrite solutions to fizzbuzz in all languages we know to farm points on such a platofrm. So we’ll just write it once and call it. Abstracting it away! (Writing external REST API calls is so much more fun!)
Let’s start with a simple fizzbuzz function for a single number in PHP. Why PHP? Because I happen to have a webhoster that natively supports it, making testing and iterating on it fast and simple. Here is the fizzbuzz.php:
<?php
function fizzbuzz($fb)
{
if($fb<1 || !is_numeric($fb)){
return “”;
}else if($fb % 15 == 0){
return “FizzBuzz”;
}else if($fb % 3 == 0){
return “Fizz”;
}else if($fb %5 == 0){
return “Buzz”;
}else{
return $fb;
}
}
Simple and straight forward, as we would expect from a topclass fizzbuzz. Don’t use it in production just yet, though, it’s not battle tested yet.
Building on this function, we would like to offer two API endpoints:
- getting the fizzbuzz value for a single element,
- getting an array of all fizzbuzz values up to a given value.
As we are providing an API, we should stick to a common format for data. Our desired API endpoints have no complex data, so we stick with JSON, which is well supported for PHP.
Let’s take a look at exact.php the file we will use to realize the first API endpoint:
<?php
header('Content-Type: application/json');
include 'fizzbuzz.php';
$fb = $_GET["fb"];
if(!is_numeric($fb)){
echo json_encode("");
}else{
echo json_encode(fizzbuzz($fb));
}
A few points of interest, starting from the top: we add the expected content type header, to let our recipients know they receive JSON formated data. Setting headers needs to be done before any output. To ensure this condition, we defer includes after the header statements, as included files might output. We can’t trust the author of fizzbuzz.php after all.
We read the get parameter “fb” so, for now, we have to call this file using exact.php?fb=<number> to get the API response. We will fix this later, so we can call our API in a PHP-agnostic way.
Lastly, we check the read value, as we do not trust the author of our fizzbuzz function to do the right thing and return the JSON encoded response from the function. A simple call to exact.php?fb?=15 will now return the string “FizzBuzz” and our browser recognizes it as JSONdata.
We can do the same for our range endpoint, using a file called to.php. We use the same parameter name, headers and checks, but resulting in the much more interesting result of a JSON array when calling to.php?fb=15.
<?php
header('Content-Type: application/json');
include 'fizzbuzz.php';
$fb = $_GET["fb"];
if(!is_numeric($fb)){
echo json_encode("");
}else{
$results = [];
for($i = 1; $i <= $fb; $i++){
$results[] = fizzbuzz($i);
}
echo json_encode($results);
}
Time to relax.
Unfortunately, we are currently restricted to PHP files that are called with parameters. This is unpleasant to document and use, and terrible to migrate to a new platform: we might need to switch to a high-performance fizzbuzz solution once our fizzbuzz service takes off.
Our current solution, hosting it on a PHP based provider, can do that: it is running apache with an enabled rewrite engine. This allows us to specify rewrite rules in a .htaccess file in our folder:
RewriteEngine on
RewriteRule ^/?to/([0–9]+)$ /to.php?fb=$1 [NC,L]
RewriteRule ^/?([0–9]+)$ /exact.php?fb=$1 [NC,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /404.php [NC,L]
First, we have to instruct the apache http server to activate the engine. Afterwards, we can specify rewrite rules. As htaccess files can contain many declarations, a rewrite rule is initiated with the string “RewriteRule”. A regular expression defines which requests we want to capture, followed by the destination we want to rewrite to. Lastly, we have further attributes.
The rules will be applied top to bottom, once a match is found, the attributes in brackets determine if further rules are applied: L specifies no further matching after applying this rule. The other used attribute, NC, explicitly tells apache to ignore case.
Within the regular expression, we can capture information: using brackets we tell the regular expression, that we want to preserve this part of the match and inject it in the position of “$1”.
As a final touch on the htaccess file, we want to rewrite any other request to a custom 404 reaction. We add a rewrite condition, which limits the next line of the configuration, to only apply if no matching file exists. This is necessary to prevent rewriting of our filenames to.php and exact.php.
Using this file, requests to /to/15 will be forwarded correctly to our previous implementation, as well as /10232 to our exact.php file.
After finishing the productive part of our endeavour, it is time to think of deployment and providing the thirsty masses with easy access to our open source fizzbuzz solution (until we find a way to monetize a fizzbuzz SaaS of course).
You might think one of the reasons to chose PHP was simple deployment. But simple is in the eye of the beholder. If you are not already subscribed to such a service, you will need a PHP installation, an apache webserver, and maybe even a Linux! If someone deploys our software on an Nginx, it will not work, too. So the answer is obvious: docker.
To provide a docker version of our software, we require a Dockerfile. A simple textbased file specifying our requirements and actions. We can also choose a base image to use for our software. Fortunately, PHP provides apache based docker images.
FROM php:7.4-apache
RUN a2enmod rewrite
COPY src/ /var/www/html/
EXPOSE 80/tcp
We need to activate Apaches rewrite mod, which can simply be done using the run command. While run would allow us to install arbitrary software, we do not need anything else. Copy instructs docker do use our source folder at a specific place inside our docker container. Expose tells it that we require port 80 for TCP.
We can build a container image from our dockerfile using ‘docker build -t fizzbuzz .’ and run it using ‘docker run fizzbuzz -p 8080:80’, which will allow us to access the fizzbuzz API on ‘http://localhost:8080’.
Finally, we can share the generated image with all our clients or even distribute it via docker hub.
All sources can be found on GitHub. The actual docker container is also available on docker hub and running on fizzbuzz.ketzu.net.