What's new in PHP 8.1
PHP 8.1 was released on November 25, 2021. It's currently the latest PHP version. In this post, we'll go through all features, performance improvements, changes and deprecations one by one.
# New features
As with every release, PHP 8.1 adds some nice new features. Keep in mind that this list will grow over the year.
# Enums RFC
Enums will be added in PHP 8.1! If you're unsure what they can be used for, you can read about them here.
Adding enums would be a significant improvement in PHP, so I for one am very much looking forward seeing enums arrive in PHP 8.1. To give you a quick preview of what they will look like, here's a code sample:
enum Status { case Pending; case Active; case Archived; }
And this is how they will be used:
class Post { public function __construct( private Status $status = Status::Pending, ) {} public function setStatus(Status $status): void { // … } } $post->setStatus(Status::Active);
You can find an in-depth analysis of how to use enums in this post.
# Fibers RFC
Fibers — aka "green threads" — are a low level mechanism to manage parallelism. You probably won't use them directly in your applications, but frameworks like Amphp and ReactPHP will make extensive use of them.
Here's a simple example of using fibers:
$fiber = new Fiber(function (): void { $valueAfterResuming = Fiber::suspend('after suspending'); // … }); $valueAfterSuspending = $fiber->start(); $fiber->resume('after resuming');
If you want to read some more about fibers, what they can and can't do, you can read this post.
# Performance improvements PR
Dmitry Stogov has added some improvements to opcache, he calls it "inheritance cache". This feature allows links between classes to be cached, much like linked classes can be preloaded as of PHP 7.4.
Dmitry reports between a 5% and 8% performance increase thanks to this change, a nice little detail to look out for in PHP 8.1.
# Array unpacking with string keys RFC
Array unpacking was already allowed in PHP 7.4, but it only worked with numeric keys. The reason string keys weren't supported before is because there wasn't any consensus on how to merge array duplicates. The RFC cleanly solves this by following the semantics of array_merge
:
$array1 = ["a" => 1]; $array2 = ["b" => 2]; $array = ["a" => 0, ...$array1, ...$array2]; var_dump($array); // ["a" => 1, "b" => 2]
# new
in initializers RFC
This RFC allows you to use the new
keyword in function definitions as a default parameter, as well as in attribute arguments and other places.
class MyController { public function __construct( private Logger $logger = new NullLogger(), ) {} }
You can read all about this feature in this dedicated post.
# Readonly properties RFC
Class properties can be marked as readonly, meaning they can only be written once.
class PostData { public function __construct( public readonly string $title, public readonly DateTimeImmutable $date, ) {} }
Trying to change a readonly property after it has been initialized will result in an error:
$post = new Post('Title', /* … */); $post->title = 'Other'; Error: Cannot modify readonly property Post::$title
If you want to learn more about readonly properties in depth, you can read my followup post.
# First-class callable syntax RFC
You can now make a closure from a callable by calling that callable and passing it ...
as its argument:
function foo(int $a, int $b) { /* … */ } $foo = foo(...); $foo(a: 1, b: 2);
# Pure intersection types RFC
You already know about union types in PHP 8.0, and intersection types are a similar feature. Where union types require the input to be one of the given types, intersection types require the input to be all of the specified types. Intersection types are especially useful when you're working with lots of interfaces:
function generateSlug(HasTitle&HasId $post) { return strtolower($post->getTitle()) . $post->getId(); }
If you like this style of programming, you'd need to create a new interface Sluggable
and implement it in $post
, intersection types get rid of that overhead.
# New never
type RFC
The never
type can be used to indicate that a function will actually stop the program flow. This can be done either by throwing an exception, calling exit
or other similar functions.
function dd(mixed $input): never { // dump exit; }
never
differs from void
in that void
still allows the program to continue. This might seem like a novelty feature but it's actually a quite useful one for static analysers.
# New array_is_list
function RFC
You've probably had to deal with this once in a while: determine if an array's keys are in numerical order, starting from index 0. Just like json_encode
decides whether an array should be encoded as an array or object.
PHP 8.1 adds a built-in function to determine whether an array is a list with those semantics, or not:
$list = ["a", "b", "c"]; array_is_list($list); // true $notAList = [1 => "a", 2 => "b", 3 => "c"]; array_is_list($notAList); // false $alsoNotAList = ["a" => "a", "b" => "b", "c" => "c"]; array_is_list($alsoNotAList); // false
Do you want to stay up-to-date about PHP 8.1's development? Subscribe to my newsletter and receive occasional updates:
# Final class constants RFC
Class constants in PHP can be overridden during inheritance:
class Foo { public const X = "foo"; } class Bar extends Foo { public const X = "bar"; }
As of PHP 8.1, you can mark such constants as final
in order to prevent this:
class Foo { final public const X = "foo"; } class Bar extends Foo { public const X = "bar"; Fatal error: Bar::X cannot override final constant Foo::X }
# New fsync
function RFC
PHP 8.1 adds the fsync
and fdatasync
functions to force synchronization of file changes to disk and ensure operating system write buffers have been flushed before returning.
$file = fopen("sample.txt", "w"); fwrite($file, "Some content"); if (fsync($file)) { echo "File has been successfully persisted to disk."; } fclose($file);
Because disk synchronization is a file system operation, the fsync
function will only work on plain file streams. Attempting to sync non-file streams will emit a warning.
# Explicit octal integer literal notation RFC
You can now use 0o
and 0O
to denote octal numbers. The previous notation by prefixing a number with 0
still works as well.
016 === 0o16; // true 016 === 0O16; // true
# Breaking changes
While PHP 8.1 is a minor version, there will be some changes that might technically be a breaking change, and deprecations as well. Let's discuss them one by one.
# Internal method return types RFC
Chances are you might run into this deprecation notice when upgrading to PHP 8.1:
Return type should either be compatible with IteratorAggregate::getIterator(): Traversable, or the #[ReturnTypeWillChange] attribute should be used to temporarily suppress the notice
You might notice this error pop up when using phpunit/phpunit
, symfony/finder
and some other popular open source packages. What's happened is that internal functions are starting to use proper return types. If you're extending a class from the standard library (like IteratorAggregate
), you'll need to add those return types as well.
The fix is simple: update your vendor code if the error occurs in a third-party package (most of those are already fixed with their newest releases). If the error occurs in your code you can either add the ReturnTypeWillChange
attribute, suppressing the error until PHP 9.0. Here's an example of a class extending DateTime
:
class MyDateTime extends DateTime { /** * @return DateTime|false */ #[ReturnTypeWillChange] public function modify(string $modifier) { return false; } }
Or you can simply add the return type:
class MyDateTime extends DateTime { public function modify(string $modifier): DateTime|false { return false; } }
# Restrict $GLOBALS
usage RFC
A small change to how $GLOBALS
is used will have a significant impact on the performance of all array operations. Nikita does a fine job explaining the problem and solution in the RFC. The change means that some edge cases aren't possible to do any more with $GLOBALS
. "What is no longer supported are writes to $GLOBALS taken as a whole. All the following will generate a compile-time error":
$GLOBALS = []; $GLOBALS += []; $GLOBALS =& $x; $x =& $GLOBALS; unset($GLOBALS);
On top of that, passing $GLOBALS
by reference will generate a runtime error:
by_ref($GLOBALS); // Run-time error
Nikita analysed the top 2000 packages on Packagist, and only found 23 cases that will be affected by this change. We can conclude the impact of this — technically breaking — change will be low, which is why internals decided to add it in PHP 8.1. Remember that most of us will win by this change, given the positive performance impact it has everywhere in our code.
# Resource to object migrations
These changes are part of the long-term vision to convert all resources to dedicated objects. You can read more about it here.
Fileinfo functions with finfo
objects
Functions like finfo_file
and finfo_open
used to accept and return resources. As of PHP 8.1, they work with finfo
objects.
IMAP functions with IMAPConnection
objects
Just like the fileinfo change, IMAP functions like imap_body
and imap_open
no longer work with resources
# Deprecate passing null to non-nullable arguments of internal functions RFC
This change is simple: internal functions currently accept null
for arguments that are non-nullable, this RFC deprecates that behaviour. For example, this is currently possible:
str_contains("string", null);
In PHP 8.1, these kinds of errors will throw a deprecation warning, in PHP 9 they will be converted to type errors.
# Autovivification on false
RFC
From the RFC:
PHP natively allows for autovivification (auto-creation of arrays from falsey values). This feature is very useful and used in a lot of PHP projects, especially if the variable is undefined. However, there is a little oddity that allows creating an array from a false and null value.
You can read the details on the RFC page. In summary, this behaviour is deprecated:
$array = false; $array[] = 2; Automatic conversion of false to array is deprecated
# Other small changes
With every release, there's a bunch of very minor changes to the language. All of them are listed in the UPGRADING guide on GitHub and the small deprecations RFC, make sure to check it out if you want to know every little detail.
Here's a summary of the most significant changes:
-
MYSQLI_STMT_ATTR_UPDATE_MAX_LENGTH
no longer has an effect -
MYSQLI_STORE_RESULT_COPY_DATA
no longer has an effect -
PDO ::ATTR_STRINGIFY_FETCHES now also works with booleans - Integers and floats in PDO MySQL and Sqlite result sets will be returned using native PHP types instead of strings when using emulated prepared statements
- Functions like
htmlspecialchars
andhtmlentities
now also escape'
by default to'
; malformed UTF-8 will also be replaced with a unicode character, instead of resulting in an empty string - The
hash
,hash_file
andhash_init
have an extra argument added to them called$options
, it has a default value of[]
so it won't affect your code - New support for
MurmurHash3
andxxHash
That's it for now, keep in mind I'll regularly update this post during the year, so make sure to subscribe if you want to be kept in the loop. Are you excited for PHP 8.1? Let me know on Twitter!