Apr 8, 2015

The little, oft-unrecognized, Laravel goodies

!
Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

I was talking over a particular architecture issue with Taylor Otwell and happened to show him some unrelated code as an example. He paused our conversation so that he could show me a quick refactor, and straight up embarrassed me, so I had to share it with you, dear readers.

Here's my code. I write these little microservices all the time--they just sit in front of another service and cache it. That's it. Sometimes I modify the output or the headers, sometimes I crunch some things or mangle others. But the simplest form is just caching a slow or unreliable service.

<?php // routes.php

class fakeApiCaller
{
    public function getResultsForPath($path)
    {
        return [
            'status' => 200,
            'body' => json_encode([
                'title' => "Results for path [$path]"
            ]),
            'headers' => [
                "Content-Type" => "application/json"
            ]
        ];
    }
}

Route::get('{path?}', function($path)
{
    $cacheLengthInMinutes = 60;
    $headersToPassThrough = [
        'Content-Type',
        'X-Pagination'
    ];

    if (Cache::has($path)) {
        $result = Cache::get($path);

    } else {
        $myCaller = new fakeApiCaller;

        $result = $myCaller->getResultsForPath($path);

        Cache::put($path, $result, $cacheLengthInMinutes);
    }

    // Pass through specified headers
    $headers = [];

    foreach ($headersToPassThrough as $header_name) {
        if (array_key_exists($header_name, $result['headers'])) {
            $headers[$header_name] = $result['headers'][$header_name];
        }
    }

    return response($result['body'], $result['status'], $headers);
})->where('path', '.*');

It's clear, and I felt pretty good about it not wasting too much time or code. A bit long, yes, but whatever.

And then Taylor passed this back:

<?php // routes.php

class fakeApiCaller
{
    public function getResultsForPath($path)
    {
        return [
            'status' => 200,
            'body' => json_encode([
                'title' => "Results for path [$path]"
            ]),
            'headers' => [
                "Content-Type" => "application/json"
            ]
        ];
    }
}

$app->get('{path?}', function($path)
{
    $result = Cache::remember($path, 60, function() use ($path) {
        return (new fakeApiCaller)->getResultsForPath($path);
    });

    return response($result['body'], $result['status'], array_only(
        $result['headers'], ['Content-Type', 'X-Pagination']
    ));
})->where('path', '.*');

This takes advantage of several things mine didn't.

First, he used $cache->remember(), which was a Laravel feature I didn't even know existed. Remember is for exactly what I'm doing, so it simplifies the process of saying "Give me the value of X, and if that wasn't set yet, get it from Y and then cache it at X and return it."

Second, he new'ed up the fakeApiCaller inline, which means he didn't need a spare line for that when we were only using it once.

Third, he passed the $cacheLengthInMinutes directly to Cache::remember. Using this or not depends on the familiarity of your team (and potential future devs) with Laravel. Just having a number there could be confusing, so if you anticipate trouble there, just extrat the 60 out as a variable like $cacheLengthInMinutes.

Fourth, he used Laravel's array_only syntax to easily only extract array items from $result['headers'] that matched my "desired pass-through" array. Again, if you have folks who aren't used to array_only, you could extract the second parameter out to a $headersToPassThrough variable.

Concludadore

So, you might be looking at this saying, "Matt, really? That's not that much different!" And you're right. But to me, taking advantage of little tools and helpers--for example, I've been absolutely in love with array_walk lately--in your tiniest lines of code is a huge aspect of being a clear and concise programmer. If you're faithful in the little things...

I spend so much of my time at the top level that I miss out on a lot of opportunities for getting better as a line-by-line in-the-trenches programmer. I miss it, and I need it, which is (part of) why I have so many side projects. And little chances like this to up the quality and concision (but not cleverness!) of my code gives me great joy.

As an aside, my friend Zack Kitzmiller re-shared the Zen of Python, which I've read many times but feel like I should tattoo on my forearms.


Comments? I'm @stauffermatt on Twitter

Subscribe

For quick links to fresh content, and for more thoughts that don't make it to the blog.