Hello, Acronym-Filled World: Playing with PHP + API

paper figure of blond-haired white person in collared shirt and glasses waves at camera in front of "Hello world" echo statement written in PHP

In a recent conversation over lunch, a former student-turned-career-advisor referred to me as a “technology generalist in a world full of specialists.” That was a really polite way of saying I know a little bit about a lot and tend not to have years of detailed experience with one small thing I can focus my attention on and build a reputation for. I’m adaptive, with broad and shallow experience in a number of areas. For example, I have tinkered with PHP, and I know what an API is, but I had never created anything with the two.

I decided I should improve my experience with coding a bit. As with so many things, I know enough to be dangerous. But I’d never really applied coding to anything practical. When I re-launched this website over the summer I wanted to add a feature that would show silly statistics culled from tracking platforms I use and update those numbers live as a curiosity. As of last week, I finally got that working.

The Gist

In the footer of every page on my site, you’ll find lists of links. The Connect column lets you reach out to me through various platforms. The Explore column shows the main categories of content on this site. And now the Track column presents the number of miles I’ve driven, kilometers I’ve run, and books I’ve read since the start of 2020.

Each of these numbers comes from a different source. My driving gets automatically tracked by the very nifty (and very geeky) Teslascope, which gathers statistics including my odometer reading every few minutes while I’m on the road. I use Smashrun to track my jogging because it provides useful, easy-to-understand visualizations about my progress, and it integrates data from nearly any source I throw at it. When I finish a run, Smashrun updates within minutes. And finally, Goodreads provides a handy tool to track the books I want to read and the material I have read. When I finish a book, I mark it as read in Goodreads before returning it to the library. It’s a great way to see what I’ve had my nose in over time.

Each of these sources provides an API for its users.

Quick Definition: API

For those unfamiliar, an Application Programming Interface allows different programs on different computers to talk to each other and share information. When a service/platform offers an API, they tell programmers the kinds of data they’re willing to share if you ask politely. It just so happens that Teslascope, Smashrun, and Goodreads each provides the one data point I wanted to display.

To get that Track column to work, all I had to do was teach my website how to ask (politely!) for the data. This is the thing I’d never bothered to learn to do. I understood the principle but had zero experience with its practical use from the programming side of things.

The Process

Reading existing code and figuring out what others have done helped me tinker with my website and with Hybrid Pedagogy‘s for many years. I would see how the designer made things work, and I would shift them around a bit to suit my needs, changing layouts or displaying different content. But I had never created my own code. With a little help from Codecademy, I learned how to create php code, rather than just interpreting what others have already written.

Any writing class worth its salt will tell you that writing never comes from the author-as-solo-genius. Inspiration and precedent go into any text. Coding is text. When I learned to write my own code, really I was just learning the context and the cultural norms used by coders. Then, when I had an idea, I knew how to write it according to community standards. I also learned how to properly articulate questions when I got stuck.

Running with Widgets

To make my Track column work, I created a new widget in WordPress. That text widget was going to contain code to execute, so I needed a way to get WordPress to run any code within the text. For that, I found a nifty hint for a quick function that does the trick.

add_filter('widget_text','execute_php',100);
function execute_php($html){
     if(strpos($html,"<"."?php")!==false){
          ob_start();
          eval("?".">".$html);
          $html=ob_get_contents();
          ob_end_clean();
     }
     return $html;
}

Adding that to the functions.php file in my child theme — you are using a child theme, right? — was all it took.

But an issue I discovered later: WordPress might now run code in my widgets, but it won’t display code in my widgets when I edit them. Any PHP content in a widget gets mangled when I go back to editing the widget. Keep a backup copy of all code in a separate file/place.

Fetching the Data

Next, I needed to learn how to get the data I needed. Each site I draw from has an API reference explaining what information it offers and how to request it. (See documentation for Teslascope APIs, Smashrun APIs, and Goodreads APIs.) These references told me what I had to do to request the information and what to expect in return.

Because this was my first time interacting with APIs, I needed a playground in which I could experiment, make mistakes, and see the consequences of errors. For that, I used Postman, a powerful app designed to help coding teams manage their API calls and streamline their development efforts. Luckily for me, Postman is easy enough to use for simple stuff that I didn’t get overwhelmed and could preview what each API sent in return to my requests.

As an aside, Postman allowed me to realize a problem with my initial plan. At first, I thought I would fetch odometer readings directly from my car, essentially pinging the vehicle every time someone visits this site. That created two problems. First, random page loads could lead to extra battery drain by preventing the car from going to sleep properly. Second, any requests that did wake the car from sleep would take forever to load, essentially freezing my website. The first problem could get annoying; the second was a dealbreaker. I had to find another way to access vehicle data, and I discovered Teslascope offered just the solution — cached data updated frequently enough for my needs, as well as the decency to not completely drain my car battery. Win!

Asking Politely

I mentioned above that one must ask politely to get servers to give up their data. The details are beyond the scope of this post, but to ensure the right people have access to server data, requests have to include an authorization key. These keys get generated through a variety of means, from simple forms on a site to a back-and-forth automated process called OAuth. While OAuth is robust and secure, it’s overkill in my case because I’m one person accessing only my data. OAuth works well if I wrote an app allowing anyone to access their own data — I wouldn’t want to know their login information, and my app on one person’s device would have to ask for different info than my app on someone else’s device. That flexibility necessitates a system that asks for API keys on the fly.

Thankfully my needs are simpler. Each API I use allows some kind of shortcut for end users to pull only their data. Sure, I still had to generate and store my key, but I don’t have to program my website to do that automatically. One thing at a time. Baby steps.

Nothing’s Ever Easy

I ran into one last hurdle, which took me longer to untangle than expected: Teslascope and Smashrun provide JSON data, which is easy to work with. Goodreads stubbornly and inexplicably provides XML data, despite repeated requests for them to switch to JSON. I figured out how to process the JSON data in minutes. That dang XML code took way too long for me to untangle, particularly because the one number I needed — the count of books on my “Read” shelf — came as the attribute of a nested key, rather than as a key’s value.

I felt like I was using a microscope to find a grain of sand (not impossible, but tedious) and along the way realized I had to name the color of that sand once I found it. Saying, “Here it is!” wasn’t enough. Ugh.

Coding the Widget

Armed with my API references and keys, and ready to execute code placed in a WordPress widget, I could now create the code to fetch and display the stats. First, I had to get and parse data from the three sources:

Smashrun

$srurl = 'https://api.smashrun.com/v1/my/stats/2020?client_id=client&access_token=no-peeking-at-my-token-get-your-own';
$srdata = file_get_contents($srurl);
$runinfo = json_decode($srdata);

Teslascope

$tsurl = 'https://teslascope.com/api/vehicle/Friend?api_key=no-peeking-at-my-key-get-your-own';
$tsdata = file_get_contents($tsurl);
$driveinfo = json_decode($tsdata);

Goodreads

$grurl = 'https://www.goodreads.com/review/list?id=342436&shelf=read&page=1&per_page=1&key=no-peeking-at-my-key-get-your-own';
$grdata = file_get_contents($grurl);
$bookinfo = simplexml_load_string($grdata);

Now I’m ready to display the numbers I gathered. I made sure to round distances to whole numbers for clarity and simplicity. Smashrun allows me to easily pull my total distance for this year alone. For the other two, a little last-minute subtraction was necessary to ignore everything earlier than Jan 1, 2020. Because the lists in the site footer are intended to be links to learn more, each stat label links visitors to my status page on the respective service. That doesn’t exactly meet Goodreads’ attribution requirements, but I think it’s close enough for a single-user operation.

Here’s the code I use:

[car icon] <? echo round($driveinfo->response->odometer - 37000) ?> <a href="http://teslascope.com/vehicle/Friend/Sparky">miles driven</a>
[running icon] <? echo round($runinfo->totalDistance) ?> <a href="http://smashrun.com/chris.friend">kilometers run</a>
[reader icon] <? echo ($bookinfo->books[0]['total'] - 437) ?> <a href="https://www.goodreads.com/user/show/342436-chris-friend">books read</a>

And there we have it! That’s the magic behind the curtain and the rationale behind the solution.

This generalist sure is proud of himself for making that all work. Am I now applying for all the coding jobs everywhere? No. But I’m able to say I can do this, and I can point people to my site as a demonstration. For now, that’ll do.