PHP Ain’t Dead

Logic's arrange view for "PHP ain't dead"

That PHP isn’t dead is something that every PHP developer knows. Every other kind of developer seems to think that it’s still the mess it was in version 5, back in 2005. Not surprisingly, things have changed, and PHP has risen from the ashes of its former self to form a fantastically flexible, fast, and ever more reliable language for web development. Quite a bit of this momentum is owed to the long-standing use of PHP by WordPress, representing a big chunk of the entire web, but WordPress has not been what pushes PHP forward (if anything it’s been holding it back through its conservative upgrade policy and the monstrous inertia of its ecosystem); much of the credit for the steady improvement of PHP can be laid at the feet of two frameworks: Symfony and Laravel (which I’ve sung about before). Laravel builds on top of many Symfony components, and adds a big “batteries included” layer that makes web apps and APIs amazingly easy and elegant to build. The synergy between Laravel and PHP feeding back into each other over the last 10 years has generated immense goodwill, resulting in an ecosystem that is unmatched in pretty much any other language, leading to amazing spin-offs like Tailwind (which I also wrote a song about), LiveWire, and Filament.

The minutiae of this history is all up for debate, but that’s not for here. One meme that has remained stubbornly hard to shift is that “PHP is dead“, when it’s really just not true. So I thought I’d write a song about it.

I had been recently entertained by the ridiculous country comedy of Biscuit Beats; I thought that a country song would be fun, and I’d never written one before. A bit of searching led me to some basic pointers, such as G major being the key of choice, and some common chord progressions. Much like my other recent songs, I used some AI tools to help out with its construction; I asked Claude, ChatGPT, and Mistral for variations on what I’d found, and picked from amongst them to construct something that was what I was happy with (it took a fair bit of filtering – many suggestions were terrible!).

I had no trouble coming up with lyrics as there are so many things to say on this subject, and I’m keen to use self-deprecation and humour, however, there are quite a lot of words in this song – country songs seem quite verbose! I really wanted a male voice to sing this. I didn’t have one for my preferred voice synth, but I do at least have a built-in one of my own, though I’m somewhat handicapped by a British accent that isn’t the usual choice for country! For a change I found the singing quite easy as it’s quite relaxed and fits neatly into my natural range, but frankly you don’t want to hear my unedited, uncorrected original! I did use Synthesizer V for backing vocals though.

I played the acoustic guitar parts, and they sound clean and glossy, but I really needed some nice bendy, twangy country electric guitar, and I’m not up to playing that, so I enlisted the help of Doc Brown on Fiverr.com, who delivered some nice tracks on Christmas eve.

I invested in Sonible’s smart:EQ 4, as I really needed some help at the mixing stage with masking issues, and it’s a great plugin, really helping to stop tracks treading on each other. I was surprised to discover that modern country really goes in for heavy, obvious pitch correction, so I had to go with the flow… Logic’s Flex Pitch is great for cleaning up dodgy vocals (ahem), but not so good as an effect. Logic has a stock pitch correction plugin that can be pushed into “robot” territory, but it’s not great, so I also sprung for a $10 special offer on Brainworx’ bx_crispytuner for more AutoTune-style options, and that’s really quite fun.

Drums are by Logic’s Drummer player with the “Sunset” kit, though I had trouble getting hard-hitting sounds out of the stock kits; smart:EQ helped here too. Bass and keyboard parts are also built using Logic’s players (as I used on other recent songs) with modelled Bass and ES2 instruments.

[verse]
Well, they say the web's a-changin', and new tech's movin' fast,
JavaScript, it swings both ways, thinks it leads the class.
I’ve built so many sites, it seems like forever,
seen the latest thing come and go thinkin' it's clever
Some might think I’m soft in the head
when they hear me say that PHP ain't dead

[chorus]
PHP ain't dead, and it's not going away,
it's getting even better each and every day.
There’s no need to switch to something shiny and new
just to have it break when you’ve got work to do.
You know you can keep on tryin’
in a language that just ain’t dyin’
It aint’ recedin’, it’s getting further ahead,
but in case you ain’t heard, PHP ain’t dead

[verse]
They say my code is legacy, that I’m stuck in the past,
but I'm still cranking out code that's built to last.
The syntax might be messy, the typing might be loose
but millions of sites prove it’s still got juice.
You shouldn’t always believe what you’ve read
but I’m telling you that PHP ain’t dead

[chorus]

[verse]
Rust and Go can talk their smack all day,
it’s the language for the web that can find a way.
It’s got the best darn framework in the whole wide web,
a set of tools with a sense of style,
and package management that’ll make you smile.
I don’t care what anyone else said
I can keep on codin’ ‘cause PHP ain't dead

[chorus]

[outro]
So let’s raise a cold one for code that stays runnin’
a community that rocks, and features keep on comin’
Despite so many rumours of its demise,
PHP’s very much alive

If you like this song, please consider supporting me by buying my album, “Developer Music” on Bandcamp, and sharing links to my song posts on here.

Laravel duplicate key error despite unique validation

In a Laravel API, it’s really common to create users with an endpoint like this in a user controller:

public function store(Request $request): UserResource|JsonResponse
{
    $validator = Validator::make(
        $request->all(),
        [
            'email' => 'required|string|max:255|email|unique:users',
            'name'  => 'required|string|max:255',
        ],
        [
            'email.unique' => 'That email address already has an account.',
        ]
    );
    if ($validator->fails()) {
        return response()->json(
            [
                'error'   => true,
                'message' => $validator->errors()->all(),
            ],
            Response::HTTP_UNPROCESSABLE_ENTITY
        );
    }
    $user = User::create(
        $request->only(
            [
                'email',
                'name',
            ]
        )
    );Code language: PHP (php)

There’s a problem here though – that unique validation on the email field is subject to a race condition. If two requests are received very close together, both can pass validation, but then the second one will fail with a duplicate key error on the User::create call. While that sounds unlikely, it happens for real sometimes, and you’ll see something like this in your web logs when it does:

192.168.0.1 - - [06/Oct/2023:07:08:54 +0000] "POST /users/ HTTP/2.0" 201 1276 "-" "okhttp/4.9.2"
192.168.0.1 - - [06/Oct/2023:07:08:55 +0000] "POST /users/ HTTP/2.0" 500 17841 "-" "okhttp/4.9.2"Code language: JavaScript (javascript)

The 201 response is a successful creation, but it’s followed a second later by a 500 failure for the duplicate request. The Laravel log will then contain one of these:

[2023-10-06 07:08:55] staging.ERROR: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'user@example.com'
 for key 'users.users_email_unique' (Connection: mysql, SQL: insert into `users` (`email`, `name`) values (user@example.com, Test)Code language: JavaScript (javascript)

To deal with that we can trap the creation error, and return an error response that looks the same as the validation error:

try {
    $user = User::create(
        $request->only(
            [
                'email',
                'name',
            ]
        )
    );
} catch (QueryException $e) {
    //1062 is the MySQL code for duplicate key
    if ($e->errorInfo[1] !== 1062) {
        //Rethrow anything except a duplicate key error
        throw $e;
    }
    return response()->json(
        [
            'error'   => true,
            'message' => 'That email address already has an account.',
        ],
        Response::HTTP_UNPROCESSABLE_ENTITY
    );
}Code language: PHP (php)

This way, as far as the client is concerned, it was a straightforward validation failure with an appropriate 422 error code, and we don’t get spurious 500s clogging up our error logs.

Bridging Laravel Scout to Eloquent

Laravel Scout provides an interface to external search engines such as Algolia and Meilisearch. These are typically remote indexes of the same data that you’re storing locally, but with far faster and more powerful searching and ranking capabilities. However, they have some limitations on how you can express the search itself (allowing only a single string value), and what you can do with the search result (because it comes back as a Laravel Collection and not a Query Builder). This makes it difficult to do things like perform a complex search remotely, and then filter the result further via Eloquent operations, perhaps involving different parts of your database. A typical Scout search might be:

use App\Models\Order;
$orders = Order::search('Star Trek')->get();Code language: PHP (php)

This gives us a bunch of Order instances, but we had no opportunity to ask it to do things like load relations or filter the results while it was doing it.

Fortunately it’s not difficult to bridge these two worlds. The resulting Collection contains model instances, so we can extract their IDs and use them to construct an Eloquent search that selects the same records, but locally this time (Scout already did the heavy lifting of figuring out which ones we wanted):

$builder = Order::query()->whereIn('id', $orders->keys());Code language: PHP (php)

This isn’t ideal (because it will fetch those records a second time), but it will be reasonably efficient because the IDs it searches on are exact matches for primary keys in the database.

We now have a builder that will select the same records as the Scout search did, but we can continue adding to it before requesting the final results.

$result = $builder->where('orders.name', 'like', 'a%')
    ->with(['orderItems', 'customer'])
    ->get();Code language: PHP (php)

So that’s how we can get to use Eloquent features on top of a Scout search.


After finding out that this bridging wasn’t built-in, I submitted a PR to add it, but sadly it was rejected. With the PR code in place, the syntax would have looked like this:

use App\Models\Order;
$orders = Order::search('Star Trek')
    ->toEloquent()
    ->where('orders.name', 'like', 'a%')
    ->with(['orderItems', 'customer'])
    ->get();Code language: PHP (php)

To be fair, this isn’t much of a saving in the external syntax, but it is more efficient because it can get the record IDs directly from Scout without having to load the models from the database. I don’t think that efficiency gain can be obtained from outside Scout’s own code.

I hope that helps someone!

The Good Ship Laravel

I like writing songs about open source, but I’ve never actually released any or posted them publicly, mainly because my singing is fairly terrible, and trying to find others willing and able to sing about these things seems hard! I really liked “the Wellerman” sea shanty craze of 2021, I had a thought that I should make use of the the nautical theme that runs through a lot of Laravel’s nomenclature to write a shanty of my own, that wasn’t just another cover of the Wellerman. It also occurred to me that I could semi-speak the words (in a pirate voice of course!) instead of outright singing, and that made it feel a bit less daunting. I wrote the intro first, and I liked the storytelling aspect, though as intros go it’s quite long. The first verse came quite easily as I built a list of words and kind of ticked them off the list. The timing and rhyming structure is straight Limerick, which makes things very easy. The main melody was just the result of doodling on the keyboard for a bit. I was pleased with the sailor/Taylor rhyme for the chorus, but it took me ages to come up with the rest of it. I had several failing attempts at a melody for the chorus, eventually just singing something that the words fitted, and then turning that into an accordion line, then building out everything else around it.

The instrumentation was very simple – it’s a sea shanty so we need simple folk instruments – accordion and cajon, and then a plucked upright bass to fill it out. In the final chorus I threw in some lovely blatty brass and a bit of piccolo, since the top end was kind of empty.

The Logic Pro arrangement

I recorded the vocals for the intro in July 2021, but re-recorded them later for consistency. I sang the first (lowest) line of the chorus vocals, and then did something I’ve done before – copy the track, and then use Logic’s Flex Pitch editor to shift notes around to make harmonies, generally upwards, since I’d sung a low line to start with. Having found harmonies that worked, I then re-sang the new line, as a heavily edited one doesn’t sound quite right, especially when pitch shifts are quite large. I then repeated the process for a second time, giving me a three-part harmony for the chorus. Flex Pitch let me correct pitch, but also timing – the harmonies sound so much better when they line up in time too. The low line was only possible for me to sing because at the time I was recovering from COVID and a very nasty sore throat, so while I was feeling much better, my voice was much deeper then usual, and I could hit much lower notes! Overall I found the singing much easier than the other things I’ve tried to sing because it was pitched much more comfortably for my voice.

If you like this song, you might like to have a listen to my later creation, “Tailwind“.

Software & Hardware

  • Apple Logic Pro X
  • Behringer UMC404HD USB audio interface
  • Aston Element dynamic microphone
  • Adam Audio TR5V monitors
  • KRK RP10S subwoofer
  • Arturia Minilab Mk II MIDI keyboard
  • Behringer DSP8024 Ultra-Curve Pro (room correction)
  • Mackie Big Knob passive volume control

Instruments & effects

  • Accordion, cajon, upright bass, piccolo, seagulls and waves from Logic’s standard sample library
  • Brass section from Logic’s Studio Horns instrument
  • Rowing boat sample I found from some ancient soundfonts collection
  • iZotope RX7 noise reduction
  • SSL Channel Strip (EQ, compression)
  • Logic standard compressor, EQ, de-esser
  • Logic “Space Designer” reverb
  • One of Logic’s default mastering configs for final output
Intro:
I was cast adrift in development seas
a shiver of bugs a’circlin’ me
Naught but a pair of oars and my IDE
to keep my app from drownin’

I spied at last a distant sail
I signalled for ‘elp to that caravel
As she hove to I made out her name;
’Twas the good ship Laravel!

Verse 1:
Gather ye round my developers
and I’ll spin you a yarn most eloquent
A tale of passport and breeze,
socialite and jetstream
a cloud full of vapor and elegance

We’ve resources and models and more
Controllers and actions galore
Fortified with some rum,
and a sack of enums,
we’ll build an app clients will adore


Chorus:
Train your telescope on that far horizon
Don’t get marooned on development island
We’re gonna build an app so well
On the good ship Laravel

Get on board now, every sailor
dance to the tune of cap’n Taylor
You’ve never built an app so well as
On the good ship Laravel

Verse 2:

With livewire on top of your scripts
and laracasts dishing out tips
We’ve got the best pest
to chase the rats from your tests
and artisan helping you ship

The framework’s the star, that’s for sure
but there’s packages of treasure to explore
but the best bit’s the crew,
and you can join too –
everyone’s welcome aboard

Chorus2:
Train your telescope on that far horizon
Don’t get marooned on development island
We’re gonna build an app so well
On the good ship Laravel

Get on board now, every sailor
dance to the tune of cap’n Taylor
You’ve never built an app so well as
On the good ship Laravel
The good ship Laravel

If you like this song, please consider supporting me by buying my album, “Developer Music” on Bandcamp, and sharing links to my song posts on here.

An explanation for non-Laravel folk!

A shiver is the collective noun for sharks. An IDE is an integrated development environment such as PHPStorm or VS Code; think MS Word, but for programming. An app, in this context, is a web application built in PHP. Sail is the name of a Laravel feature for managing local development environments. A caravel is a 15th century Portuguese sailing boat, exactly the kind of vessel that a stranded pirate might encounter, and also the word that gave inspiration for Laravel‘s name. “Hove to” is a sailing manoeuvre used to more or less stop a boat by pointing the sails in opposing directions, very useful when picking up castaways. A yarn is a story, often nautical, and a thread, but it’s also the name of a Javascript package manager. Eloquent is the name of Laravel’s database abstraction layer. Passport, Breeze, Socialite, and Jetstream are all Laravel features for building authentication workflows. Real clouds are made of vapor, but Laravel’s serverless service is called Vapor, and runs in the cloud. Elegance? Well, it mostly rhymes with eloquent, and is something that any framework aspires to. Models, controllers, actions, and resources are all important parts of a typical web app built in an object-oriented style; I was planning to have a line about “plundering” to go with resources, but that didn’t make it. Fortify is another Laravel authentication feature, but rum is a traditional source of income for pirates, on top of its role in providing Dutch courage. Enums are a common programming language feature, but notable because they were added natively to PHP 8.1 recently. Telescope is an in-app debugging utility. Horizon is a queue monitoring extension. Cap’n Taylor is of course Taylor Otwell, the creator of Laravel. Livewire is a toolkit for building dynamic, interactive web interfaces for Laravel apps. Laracasts provides an amazing library of training material for Laravel and related technologies, and also a great forum. Pest is a relatively new system for building automated tests that Laravel uses. Artisan is a command line tool that helps automate numerous development tasks. The crew is Laravel’s development team, but also the enormous and diverse community of developers that make Laravel far greater than a typical framework – it’s home for many of us!