What's new in PHP 8.2
PHP 8.2 is released on December 8, 2022. In this post, we'll go through all features, performance improvements, changes and deprecations one by one.
# Readonly classes RFC
Readonly properties were introduced in PHP 8.1. This RFC builds on top of them, and adds syntactic sugar to make all class properties readonly at once. Instead of writing this:
class Post { public function __construct( public readonly string $title, public readonly Author $author, public readonly string $body, public readonly DateTime $publishedAt, ) {} }
You can now write this:
readonly class Post { public function __construct( public string $title, public Author $author, public string $body, public DateTime $publishedAt, ) {} }
Functionally, making a class readonly is entirely the same as making every property readonly; but it will also prevent dynamic properties being added on a class:
$post = new Post(/* … */); $post->unknown = 'wrong'; Uncaught Error: Cannot create dynamic property Post::$unknown
Note that you can only extend from readonly classes if the child class is readonly as well.
PHP has changed quite a lot, and readonly classes are a welcome addition. You can take a look at my video about PHP's evolution if you want to as well:
# Deprecate dynamic properties RFC
I'd say this is a change for the better, but it will hurt a little bit. Dynamic properties are deprecated in PHP 8.2, and will throw an ErrorException
in PHP 9.0:
class Post { public string $title; } // … $post->name = 'Name';
Keep in mind that classes implementing __get
and __set
will still work as intended:
class Post { private array $properties = []; public function __set(string $name, mixed $value): void { $this->properties[$name] = $value; } } // … $post->name = 'Name';
If you want to learn more about why deprecations are useful and how to deal with them, you can read this followup post on how to deal with deprecations, or you can check out my vlog:
# New random extension RFC
PHP 8.2 adds a new random number generator that fixes a lot of problems with the previous one: it’s more performant, more secure, it’s easier to maintain, and doesn’t rely on global state; eliminating a range of difficult to detect bugs when using PHP’s random functions.
There’s a new class called Randomizer
, which accepts a randomizer engine. Now you can change that engine, depending on your needs. For example, to differentiate between a production and testing environment.
$rng = $is_production ? new Random\Engine\Secure() : new Random\Engine\Mt19937(1234); $randomizer = new Random\Randomizer($rng); $randomizer->shuffleString('foobar');
# null
, true
, and false
as standalone types RFC
PHP 8.2 adds three new types — or something that looks like it. We'll avoid going down the rabbit hole of type safety in this post, but technically null
, true
, and false
could be considered valid types on their own. Common examples are PHP's built-in functions, where false
is used as the return type for when an error occurs. For example in file_get_contents
:
file_get_contents(/* … */): string|false
Before PHP 8.2, you could already use false
together with other types as a union; but now it can be used as a standalone type as well:
function alwaysFalse(): false { return false; }
The same now also goes for true
and null
.
# Disjunctive Normal Form Types RFC
DNF types allow us to combine union and intersection types, following a strict rule: when combining union and intersection types, intersection types must be grouped with brackets. In practice, that looks like this:
function generateSlug((HasTitle&HasId)|null $post) { if ($post === null) { return ''; } return strtolower($post->getTitle()) . $post->getId(); }
In this case, (HasTitle&HasId)|null
is the DNF type.
It's a nice addition, especially since it means that we can now have nullable intersection types, which is probably the most important use case for this feature.
# Constants in traits RFC
You can now use constants in traits:
trait Foo { public const CONSTANT = 1; public function bar(): int { return self::CONSTANT; } }
You won't be able to access the constant via the trait's name, either from outside the trait, or from inside it.
trait Foo { public const CONSTANT = 1; public function bar(): int { return Foo::CONSTANT; } } Foo::CONSTANT;
You can however access the constant via the class that uses the trait, given that it's public:
class MyClass { use Foo; } MyClass::CONSTANT; // 1
# Redact parameters in back traces RFC
A common practice in any codebase is to send production errors to a service that keeps track of them, and will notify developers when something goes wrong. This practice often involves sending stack traces over the wire to a third party service. There are cases however where those stack traces can include sensitive information such as environment variables, passwords or usernames.
PHP 8.2 allows you to mark such "sensitive parameters" with an attribute, so that you don't need to worry about them being listed in your stack traces when something goes wrong. Here's an example from the RFC:
function login( string $user, #[\SensitiveParameter] string $password ) { // … throw new Exception('Error'); } login('root', 'root'); //Fatal error: Uncaught Exception: Error in login.php:8 //Stack trace: //#0 login.php(11): login('root', Object(SensitiveParameterValue)) //#1 {main} // thrown in login.php on line 8
# Fetch properties of enums in const expressions RFC
From the RFC:
This RFC proposes to allow the use of
->
/?->
to fetch properties of enums in constant expressions. The primary motivation for this change is to allow fetching the name and value properties in places where enum objects aren't allowed, like array keys
That means that the following code is now valid:
enum A: string { case B = 'B'; const C = [self::B->value => self::B]; }
# Return type changes for DateTime::createFromImmutable()
and DateTimeImmutable::createFromMutable()
breaking
Previously, these methods looked like this:
DateTime::createFromImmutable(): DateTime DateTimeImmutable::createFromMutable(): DateTimeImmutable
In PHP 8.2 those method signatures are changed like so:
DateTime::createFromImmutable(): static DateTimeImmutable::createFromMutable(): static
This change makes a lot more sense, as it improves static insight possibilities for classes extending from DateTime
and DateTimeImmutable
. However, technically, this is a breaking change that might affect custom implementations that extend from either of those two classes.
# utf8_encode()
and utf8_decode()
deprecations RFC
In PHP 8.2, using either utf8_encode()
or utf8_decode()
will trigger these deprecation notices:
Deprecated: Function utf8_encode() is deprecated Deprecated: Function utf8_decode() is deprecated
The RFC argues that these functions have a inaccurate name that often causes confusion: these functions only convert between ISO-8859-1
and UTF-8
, while the function name suggest a more broader use. There's a more detailed explanation about the reasoning in the RFC.
The alternative? The RFC suggests using mb_convert_encoding()
instead.
# Locale-insensitive strtolower()
and strtoupper()
breaking RFC
Both strtolower()
and strtoupper()
are no longer locale-sensitive. You can use mb_strtolower()
if you want localized case conversion.
# Signature changes to several SPL methods breaking
Several methods of SPL classes have been changed to properly enforce their correct type signature:
SplFileInfo::_bad_state_ex() SplFileObject::getCsvControl() SplFileObject::fflush() SplFileObject::ftell() SplFileObject::fgetc() SplFileObject::fpassthru() SplFileObject::hasChildren() SplFileObject::getChildren()
# New n
modifier in PCRE upgrading
You can now use the n
modifier (NO_AUTO_CAPTURE
) in pcre*
functions.
# ODBC username and password escaping breaking
From the UPGRADING guide:
The
ODBC
extension now escapes the username and password for the case when both a connection string and username/password are passed, and the string must be appended to.
The same applies to PDO_ODBC
.
# Deprecate ${}
string interpolation RFC
PHP has several ways of embedding variables in strings. This RFC deprecates two ways of doing so, since they are rarely used, and often lead to confusion:
"Hello ${world}"; //Deprecated: Using ${} in strings is deprecated "Hello ${(world)}"; //Deprecated: Using ${} (variable variables) in strings is deprecated
To be clear: the two popular ways of string interpolation still work:
"Hello {$world}"; "Hello $world";
# Deprecate partially supported callables RFC
Another change, although one with a slightly smaller impact, is that partially supported callables are now deprecated as well. Partially supported callables are callables which can be called using call_user_func($callable)
, but not by calling $callable()
directly. The list of these kinds of callables is rather short, by the way:
"self::method" "parent::method" "static::method" ["self", "method"] ["parent", "method"] ["static", "method"] ["Foo", "Bar::method"] [new Foo, "Bar::method"]
The reason for doing this? It's a step in the right direction towards being able to use callable
for typed properties. Nikita explains it very well in the RFC:
all of these callables are context-dependent. The method that "self::method" refers to depends on which class the call or callability check is performed from. In practice, this usually also holds for the last two cases, when used in the form of [new Foo, "parent::method"].
Reducing the context-dependence of callables is the secondary goal of this RFC. After this RFC, the only scope-dependence still left is method visibility: "Foo::bar" may be visible in one scope, but not another. If callables were to be limited to public methods in the future (while private methods would have to use first-class callables or Closure::fromCallable() to be made scope-independent), then the callable type would become well-defined and could be used as a property type. However, changes to visibility handling are not proposed as part of this RFC.
That's all there is for now, I'll keep this list updated throughout the year. You can subscribe to my newsletter if you want to receive occasional updates!