Adam Innes · Blog

PHP 7.4 Arrow Functions and Typed Properties

· 3 min · php, php 7.4, performance

PHP 7.4.0 came out on November 28, the fourth feature release of the PHP 7 series. The announcement lists a bunch of additions, but four of them change how everyday code looks or runs: arrow functions, typed properties, unpacking inside arrays, and opcache preloading. Here’s a short tour, with examples you can paste into a PHP 7.4 CLI.

Arrow functions

Closures in PHP have always been a bit wordy, mostly because of use. Arrow functions are a shorthand for a function whose body is a single expression, and they pick up variables from the surrounding scope automatically.

<?php
$factor = 3;
$prices = [10, 20, 30];

$scaled = array_map(fn($p) => $p * $factor, $prices);
print_r($scaled); // 30, 60, 90

$count = 0;
$bump = fn() => $count++;
$bump();
var_dump($count); // int(0)

The second half shows the part that trips people up. The 7.4 migration guide describes the scope binding as implicit and by value, so the arrow function gets a copy of $count, not the variable itself. If you need to change something in the outer scope, stick with a regular closure and use (&$count). There’s also no multi statement body, so anything longer than one expression is still a job for function.

Typed properties

PHP 7.0 gave us scalar types on parameters and returns. 7.4 finally puts types on class properties.

<?php
declare(strict_types=1);

class User
{
    public int $id;
    public ?string $email = null;
}

$user = new User();
$user->email = 'adam@example.com';

try {
    echo $user->id;
} catch (Error $e) {
    echo $e->getMessage(), "\n"; // must not be accessed before initialization
}

try {
    $user->id = '42';
} catch (TypeError $e) {
    echo $e->getMessage(), "\n"; // must be int, string used
}

Every type PHP supports elsewhere works here except void and callable. Two behaviors are worth knowing before you add types to an existing codebase. A typed property without a default isn’t null, even if the type is nullable. It starts out uninitialized, and reading it throws an Error. And the strict_types setting that counts is the one in the file doing the assignment, not the file declaring the class. Without strict mode, that '42' would quietly be coerced to the integer 42.

Unpacking inside arrays

The ... operator already worked for function arguments. Now it works inside array literals, with arrays and anything Traversable.

<?php
$defaults = ['id', 'name'];
$extra = new ArrayIterator(['email']);

$columns = [...$defaults, 'created_at', ...$extra];
print_r($columns); // id, name, created_at, email

One limit: string keys aren’t supported in 7.4. Spreading an array with a string key throws an Error saying it can’t unpack an array with string keys, so for associative arrays you still want array_merge() or +.

Preloading, with caveats

Preloading is the performance headline. You point opcache.preload at a PHP script, and when the server starts, that script compiles files into shared memory. The functions and classes in them are then available to every request, as if they were built in.

opcache.enable=1
opcache.preload=/var/www/app/preload.php
opcache.preload_user=www-data
<?php
// preload.php
foreach (glob(__DIR__ . '/src/*.php') as $file) {
    opcache_compile_file($file);
}

The preloading RFC is upfront about what you give up. Preloaded code stays in memory until the server restarts. Editing the files on disk does nothing, and opcache_reset() won’t reload them either, so this belongs in production deploys that restart PHP, not on your dev box. It also doesn’t fit servers hosting several apps, or several versions of one app, because preloaded class names are shared by everything that server runs. Classes are only fully preloaded if their parents, interfaces, traits and constant values can be resolved at that point, and declarations nested inside control structures like an if aren’t preloaded. On Windows there are extra limits. Separately, the 7.4 upgrade notes say preloading as root isn’t allowed, which is what the new opcache.preload_user setting is for.

My take is that preloading is worth measuring on a framework heavy app with a steady deploy process, and easy to skip everywhere else.

Before you upgrade

7.4 also brings deprecations and a few backward incompatible changes, so read the migration guide’s sections on those before bumping your production version. For new code, though, I’d start using typed properties right away. They catch a whole class of bugs for almost no effort.

← all posts