New Features in PHP 8.1

PHP 8.1 is going to be released on November 25th, 2021 and we wanted to highlight the features we’re most excited about. This article doesn’t cover all of the new features but it does highlight the one we think will make the biggest improvement in our day-to-day development.

Enumerations

Support for enumerations is by far our favorite new feature. So much so that our next article will be just about them because they’re worth the extra focus.

Enumerations or enums for short allow us to define a new structure much like a class but that can only hold a set of allowed values.

In the past, this was handled by using public constants in a class.

class PublishedState {
    public const NotPublished = 0;
    public const Scheduled = 1;
    public const Published = 2;
    public const Deleted = 3;
}

When we need to set a variable to one of these states we’ll do the following.

$value = PublishedState::Published;

The problem with this method is that because we’re just setting a variable to a value we could, and did, do something like the following accidentally.

$value = 2 * PublishedState::Published;

This is a valid expression but causes us to have an invalid value of 4 in the $value variable.

Using enums we’ll define our values like the following.

enum PublishedState {
    case NotPublished;
    case Scheduled;
    case Published;
    case Deleted;
}

We can still set our values like so.

$value = PublishedState::Published;

var_dump($value); // enum(PublishedState::Published)

But it’s impossible to set it to an invalid case.

$value = 2 * PublishedState::Published;

// PHP Fatal error:  Uncaught TypeError: Unsupported operand types: PublishedState * int in Standard input code:8

New in Initializers

PHP 8.0 added initializers to constructors so we could define properties inside the constructor. Another wonderful addition by the PHP maintainers.

class BlogPost
{
    public function __construct(
        public string $title,
        public DateTime $created
    ) {
        // everything's already setup
    }
}

A small issue with this is that if any of the parameters were optional we had to run through a little rigamarole to initialize it to the correct value.

class BlogPost
{
    public function __construct(
        public string $title,
        public ?DateTime $created = null,
    ) {
        $this->created ??= new DateTime();
    }
}

In 8.1 we can now initialize the value directly in the function declaration.

class BlogPost
{
    public function __construct(
        public string $title,
        public DateTime $created = new DateTime(),
    ) {
    }
}

Read-Only Properties

Another improvement to classes is the ability to create read-only properties. It’s more like a write-once property because we can only write to the property once and then the second time we try to write to it we’ll get an error.

class BlogPost
{
    public function __construct(
        public string $title,
        public readonly DateTime $created = new DateTime(),
    ) {
    }
}

$item = new BlogPost("Title");
$item->created = new DateTime();

// PHP Fatal error:  Uncaught Error: Cannot modify readonly property BlogPost::$created in Standard input code:12

Pure Intersection Types

PHP 8.0 added union types so we can pass multiple types to the same function.

private function aTestFunction(int|float $param1): int|float
{
    // snip
}

If we have a function that needs to be passed an object that implements multiple interfaces before 8.1 we had to create an interface that implements all of the interfaces we’re interested in. This creates extra interfaces we need to maintain.

Thankfully PHP 8.1 adds pure intersection types so we can declare what interfaces a passed class must support for it to be valid. This is done using a single ampersand (&) character between the interfaces.

private function aTestFunction(Iterator&Countable $object): int
{
    // logic here
}

fsync() function

We haven’t had a chance to talk about how to read and write files in PHP but the basics are this.

If we want to write to a file we start with a call to the fopen() function with the file path as the first parameter and "w" as the second parameter to tell PHP we want to write to it. This returns a file pointer resource we’ll use for the rest of the interaction.

We can then call a function like fwrite() to write some data and finally fclose to close the file and tell PHP to tell the OS to write the data to our storage.

$file = fopen("transactions.txt", "w");
fwrite($file, "$1000 to Scott's Account");
fclose($file);

The potential problem with this process is that it’s possible something “horrible” could happen and our data could not be persisted in our storage because the operating system tends to cache writes for speed. If this happens data could be lost. It could be confusing if we send an email letting someone know their data has been accepted and then lose the data. :-/

In PHP 8.1 we can use the fsync() function to make sure our data synced to our storage before we continue.

$file = fopen("transactions.txt", "w");
fwrite($file, "$1000 to Scott's Account");
fsync($file);
fclose($file);

array_is_list()

When working with arrays in PHP there are two major ways we can work on them.

Numerical arrays are when all keys in the array are numbers (starting at zero).

$numericalOrder = [
    'a',
    'b',
    'c',
];

They can also be stored using a string as the key which is also known as an associative array.

$associativeArray = [
    'a' => 'a',
    'b' => 'b',
    'c' => 'c',
];

It’s hard to tell which one we’re working with without doing some tricky array manipulation.

Thankfully PHP 8.1 adds the array_is_list() function to tackle this problem. If we pass it an array and it’s not using numerical keys that start at zero it will return false.

$numericalOrder = [
    'a',
    'b',
    'c',
];

// this is true
if (array_is_list($numericalOrder) {
    echo 'Yes';
}
$associativeArray = [
    'a' => 'a',
    'b' => 'b',
    'c' => 'c',
];

// this is false
if (array_is_list($associativeArray) {
    echo 'Yes';
}

Even if we use numbers but don’t start at 0 it will return false.

$associativeArray = [
    1 => 'a',
    2 => 'b',
    3 => 'c',
];

// this is also false
if (array_is_list($associativeArray) {
    echo 'Yes';
}

Final Class Constants

Class constants in PHP can be overridden using inheritance:

class User
{
    public const table = "users";
}
 
class SystemAdminUser extends User
{
    public const table = "super_users";
}

As of PHP 8.1, we can mark such constants as final to prevent this:

class User
{
    final public const table = "users";
}
 
class SystemAdminUser extends User
{
    public const table = "super_users";
}

Go Try It

PHP 8.1 adds a lot of helpful features to make our code easier to read and maintain. We just went through our favorites of enums, being able to initialize properties in the initializers of a class, read-only properties, the fsync() function, final class constants, and the array_is_list() function.

If it’s not out already, the final release candidate of PHP 8.1 is available to test so go try out the new features and see how it can make your code easier to develop and maintain.

What’s your favorite new feature? Let us know in the comments and make sure you subscribe to our mailing list and share this everywhere.