What's New in PHP 7.4
January 31, 2020

What’s New in PHP 7.4

PHP Development
Performance

PHP 7.4 was released on November 28, 2019. As the last release before PHP 8, which is expected at the end of 2020, PHP 7.4 introduced a slew of new and updated features. 

PHP 7.4 Performance: Better Than Previous Releases

With every new PHP release we can usually expect a small performance increase, and this one seems to be no exception. This newest version also provides capabilities that help developers write more concise code. In this blog, we’ll review the key features in PHP 7.4, including:

  • Syntactic improvements
  • Engine changes
  • Class preloading
  • Foreign function interface (FFI)
  • Deprecations and other language changes

Syntactic Improvements

PHP 7.4 supports syntax improvements including the addition of:

  • Arrow functions
  • Spread operator in arrays
  • Coalescing assign operator
  • Numeric literal separator

Arrow Functions

In PHP 7.4, you can write short closures, also known as arrow functions. They provide a shorthand syntax for definingfunctions. Additionally:

  • They can only have one expression.
  • “return” keyword is not allowed.
  • They can access variables in the parent scope without the “use” keyword.
  • Arguments and return types can be type-hinted.

For example, here is a snippet of code I wrote with PHP 7.3: 

<?php

$arr = [1,2,3,4];

$new_arr = array_map(function($e) {
    return $e * $e;
}, $arr);

print_r($new_arr); //[1,4,9,16]

In PHP 7.4, I can write the snippet like this:

<?php

$arr = [1,2,3,4];
$new_arr = array_map(fn($e) => $e * $e, $arr);

print_r($new_arr); //[1,4,9,16]

Here’s another example of code written with PHP 7.3, without an arrow function, and with a “use” statement to get $coefficient in the scope of the closure:

$coefficient = 2;

$arr = [1,2,3,4];
$new_arr = array_map(function($e) use ($coefficient) {
    return $e * $e * $coefficient;
}, $arr);

print_r($new_arr); //[2,8,18,32]

This PHP 7.4 code gets the same result using the arrow function – notice that we don’t need a “use” statement, and can access $coefficient directly in the short closure:

<?php

$coefficient = 2;

$arr = [1,2,3,4];
$new_arr = array_map(fn($e) => $e * $e * $coefficient, $arr);

print_r($new_arr); //[1,4,9,16]

Coalescing Assign Operators

The coalescing assign operator in PHP 7.4 can save you time and simplify your code. It assigns the value on the right of the operator to the left parameter, if that parameter’s value is null. Otherwise, the value of the parameter remains the same. One of the most common use cases for this is when you’re importing variables from the super global variables and want to check if the value was passed, otherwise provide a default value.

The following example shows how you can improve code simplicity by comparing like statements written in using releases prior to PHP 7, PHP 7.x releases before PHP 7.4, and PHP 7.4 which uses coalescing assign operators: 

<?php

//PHP < 7
$data['key'] = isset($data['key']) ? $data['key'] : 'some_default';

//PHP 7.X < 7.4
$data['key'] = $data['key'] ?? 'some_default';

//PHP >= 7.4
$data['key'] ??= 'some_default';

Spread Operator

It is now possible to use the spread operator in array expressions:

$arr1 = [3,4,5];
$arr2 = [8,9,10];

Before PHP 7.4, you would have to write the following to merge arrays together:

$superArray = array_merge([1,2], $arr1, [6,7], $arr2);

With PHP 7.4, you can write the same statement like so: 

$superArray = [1, 2, ...$arr1, 6, 7, ...$arr2];

Numeric Literal Separator

PHP 7.4’s numeric literal separator functionality makes it possible for you to improve code readability by adding underscores between digits in numeric literals. This really helps reading large numbers like 100 million, one billion, etc.

For example, before PHP 7.4, you would write numbers like this:

$threshold = 1000000000

Now, with PHP 7.4, you can add numeric literal separators to make the value look like this:

$threshold = 1_000_000_000

Note that this is an improvement for readability only – the compiler will strip those separators out when compiling the code.

Engine Changes

PHP 7.4 includes improved engine functionality, including:

  • Typed properties
  • Covariant returns and contravariant parameters
  • Custom object serialization

Typed Properties

In PHP 7.4, class properties can be typed. Here is an example of how you can used typed properties, private and public, in a class called BankAccount

<?php

class BankAccount {
    private int $id;
    private Money $balance;
}

Covariant Returns and Contravariant Parameters

PHP releases prior to 7.4 supported invariant return types and parameter types, which meant that methods in a class implementing an interface or inheriting from another class had to return a value of the same type as the return value of methods that they override or implement. Similarly, method arguments had to be of the same type as the methods that they override or implement.

In PHP 7.4, they can be different, which is useful in many scenarios. However, for return types, the return value of a method in the child class must be more specific than the return value of the method being overridden or implemented in the parent class or interface (covariant), and for method arguments in the child class they must be less specific than the method arguments being overridden or implemented in the parent class or interface.

Here is an example of a covariant return:

<?php

interface Factory {
    public function make(): object;
}

class UserFactory implements Factory {
    public function make(): User;
}

Here is an example of contravariant parameters:

<?php

interface Concatable {
    public function concat(Iterator $input);
}

class Collection implements Concatable {
    // accepts all iterables, not just Iterator
    public function concat(iterable $input) {/* . . . */}
}

Custom Object Serialization

Two new custom object serialization methods are available in PHP 7.4: _serialize() and _unserialize(). These were added on top of the existing _sleep()/_wakeup() and Serializable interface mechanisms.

These two functions were added to solve some issues that both the __sleep()/__wakeup() mechanism and the Serializable interface mechanism had. Consider the following code snippet:

<?php

class A implements Serializable {
    private $prop;
    public function serialize() {
        return serialize($this->prop);
    }
    public function unserialize($payload) {
        $this->prop = unserialize($payload);
    }
}
class B extends A {
    private $prop;
    public function serialize() {
        return serialize([$this->prop, parent::serialize()])
    }
    public function unserialize($payload) {
        [$prop, $parent] = unserialize($payload);
        parent::unserialize($parent);
        $this->prop = $prop;
    }

In this example:

  • On serialize: A::serialize() is called, then B::serialize() is called.
  • On unserialize B::unserialize() then A::unserialize() is called.
  • This makes is so back references created during serialization will no longer be correct during unserialization.

There were also some other issues:

  • Executing code in the middle of serialization is dangerous.
    • It has led to many vulnerabilities in the past.
    • wakeup() calls are delayed until the end of serialization.
      • First, the whole object graph is constructed — and then queued __wakeup() calls are executed.
      • Former methods then see objects before they are fully unserialized => unsafe.
  • There is no general way to analyze serialized strings.
    • This is because serialize can return data in an arbitrary format.

Now consider this code snippet with the new __serialize()/__unserialize() functions: 

<?php

class A {
    private $prop_a;
    public function __serialize(): array {
        return ["prop_a" => $this->prop_a];
    }
    public function __unserialize(array $data) {
        $this->prop_a = $data["prop_a"];
    }
}
class B extends A {
    private $prop_b;
    public function __serialize(): array {
        return [
            "prop_b" => $this->prop_b,
            "parent_data" => parent::__serialize(),
        ];
    }
    public function __unserialize(array $data) {
        parent::__unserialize($data["parent_data"]);
        $this->prop_b = $data["prop_b"];
    }
}

Notice that instead of calling serialize()/unserialize(), the method directly returns the serialized object in an array, and unserialize() only accepts an array as its argument.

Class Preloading

Class preloading gives developers the ability to specify the files that get compiled at PHP startup, which can be useful in many situations, one of which is to preload framework files. However, if these files change, the server must be restarted.

To use this feature, pass a file path to the opcache.preload directive in php.ini: opcache.preload=/path/to/project/preload.php

In it, you must call opcache_compile_file(...) or require_once(...) for every file you want cached.

There are a few caveats to class preloading:

  • Unlinked classes can’t be preloaded. Dependencies, interfaces, traits, and parent classes must also be preloaded.
  • If an attempt is made to load an unlinked class, the compiler issues a warning. It will still work. However, all the classes won’t be preloaded.
  • require_once can be used to rely on the autoloader to load all the dependencies, traits, interfaces, and parent classes.

For more information on class preloading, see https://stitcher.io/blog/preloading-in-php-74.

FFI

The Foreign Function Interface (FFI) API has existed for years in languages Python and LuaJIT. In PHP 7.4, it is a new extension that provides a simpler way to call native functions and access native variables. FFI can be used to include external libraries written in C with relative ease and allows for rapid prototyping without having to write a complete extension. 

To use PHP FFI, you need to provide a header (.h) file with the functions that you want to expose through FFI, and then load the shared library that implements them with FFI::load(). Once you do that, functions and data structures defined in the .h and implemented in the shared library become accessible inside your PHP script through the FFI object.

You can learn more about the FFI API in these resources: 

Deprecations and Other Language Changes

PHP 7.4 introduces some additional changes that are important to know about. Let’s start with deprecations. 

Most, if not all, programming languages use a right-associative ternary operator. However, PHP uses left-associative, which can be confusing for developers. Starting with PHP 7.4, a deprecation warning will result for the following code unless parentheses are used:

1 ? 2 : 3 ? 4 : 5; // deprecated
(1 ? 2 : 3) ? 4 : 5; // ok

In PHP 8, deprecation will become a compile time error. 

Here’s a quick summary of the other changes in PHP 7.4:

  • Exceptions are now allowed in _toString(). They were previously prohibited because of a vulnerability.
  • The array_merge function can be called without arguments or any parameter, where formerly one parameter was required. This is to support usage of the spread operator, and the edge case where a parameter passed to array_merge might be empty.
  • Upgrades were made to proc_open() so it can execute programs by passing an array instead of a string, and without going through a shell. 

Learn More About PHP 7.4

Want to learn more about the new features in PHP 7.4? Watch my one-hour recorded webinar, titled: PHP 7.4 Is Here! Learn What’s New.

Watch Webinar