td204

Web development, one post per year since 2002

2017 in review: Laravel Mix, and taking money

Last year I moved to webpack and complained about the config. In January Laravel Mix arrived, a thin wrapper over exactly that, I put it in a project, and the config went from ninety lines to six. That was most of my 2017 front-end story.

The other half of the year was a payment integration, which is the first time I wrote code where a bug costs money directly rather than eventually.

Six lines instead of ninety

const mix = require('laravel-mix');

mix.js('resources/js/app.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css')
   .version();

That is the whole build for a normal project: bundling, compiling, cache-busting filenames. When I need something unusual the underlying bundler config is still there to extend, and I have needed that maybe three times.

The point is not "use Mix". It is that a tool with sensible defaults and an escape hatch beats a tool with only an escape hatch, and I should have gone looking for one a year earlier instead of learning a config format I did not want to know.

Integrating a payment provider

First serious one. Things I did not expect to be the hard parts:

The webhook is the source of truth, not the redirect. The customer's browser coming back to your success page proves nothing. They can close the tab, lose signal, or hit back. The status update that arrives server to server is the one you act on, and it may arrive before the redirect does.

Every handler must be idempotent. The same status notification will arrive more than once. If processing it twice sends two confirmation emails, that is your bug, not the provider's.

public function handle(Notification $notification)
{
    $order = Order::findOrFail($notification->orderId());

    // Already in a final state? Do nothing, and say so with a 200.
    if ($order->isPaid() || $order->isCancelled()) {
        return response('OK');
    }

    $order->applyStatus($notification->status());

    return response('OK');
}

Log the raw payload before you parse it. When a provider changes a field and your parser throws, the only thing that tells you what actually happened is the bytes you stored.

Responsive images, done properly at last

Also this year: I stopped shipping one large hero image to every device. srcset alone is not enough, because without sizes the browser assumes the image is the full viewport width and happily downloads a 1600px file for a 640px slot.

<img src="hero-800.jpg"
     srcset="hero-400.jpg   400w,
             hero-800.jpg   800w,
             hero-1600.jpg 1600w"
     sizes="(min-width: 1100px) 640px,
            (min-width: 700px) 60vw,
            100vw"
     alt="Rain over the harbour"
     width="1600" height="900">

The catch nobody warns you about: sizes has to match your CSS, and nothing keeps them in sync. Change a column width in the stylesheet and this attribute starts lying silently. I generate it server-side from the same breakpoint values the layout uses.

Teaching design patterns to a room of students

In February I gave a guest lecture on design patterns. Not in PHP: the course was in C#, so I spent a fortnight rebuilding singleton, builder and abstract factory in a language I do not write for a living.

That turned out to be the point. A pattern you can only express in your daily language is not a pattern you have understood, it is a habit. Porting them stripped out everything framework-shaped and left the actual idea, and I came back and wrote noticeably better PHP for it.

Preparing to teach something remains the fastest way I know to find the holes in my own understanding, and it is much cheaper than finding them in production.

Hardware, briefly

Outside client work, 2017 was the year I ran mining rigs. There are still GPU BIOS images on one of my servers: three versions of the same card, one original and two with the memory timings shifted, because that is how you tuned a card that year.

The part that turned out to be transferable was the monitoring. A rig that stops earning does not announce itself, so I wrote a small PHP endpoint the machines called on a schedule. If the pattern of calls went wrong, it emailed me. And critically, it would not email again for an hour:

$last = file_get_contents('lastmail.txt');

if (!empty($last) && (time() - $last) < 3600) {
    $mail = false;   // already sent within the last hour
}

That rate limit is the whole lesson. My first version had no such check, sent an email every few minutes, and within two days I was ignoring them, which is worse than having no alerting at all. An alert you have learned to skip is not an alert.

I have written the same guard into every notification since.

In short

Laravel Mix made webpack usable, I shipped my first payment integration, taught design patterns to students, and ran mining rigs on the side.

The year in commits

Commits by month in 2017: 1045 in total, peaking at 146 in May.
Jan 2017: 115 commits J Feb 2017: 141 commits F Mar 2017: 66 commits M Apr 2017: 53 commits A May 2017: 146 commits 146 M Jun 2017: 100 commits J Jul 2017: 44 commits J Aug 2017: 100 commits A Sep 2017: 51 commits S Oct 2017: 84 commits O Nov 2017: 33 commits N Dec 2017: 112 commits D