This article provides an overview of new features and deprecations in PHP 8.2. PHP 8.0 already set a strong base for these and upcoming improvements. Original plan to release PHP 8.2 was on November 24th 2022, but it was posponed for December 8th 2022.
In this article, we cover what is new in PHP 8.2. Our focus is on new features, deprecation and breaking changes. At the end of the article you will find list of the official references documenting all changes in the details.
PHP 8.1 introduced readonly
properties. With this, developers are able to define class properties that are initialized only once in the scope of their declaration. PHP 8.2 built upon this by adding feature to declare the entire class as readonly
.
How is this helping developers?
Well, previously in PHP 8.1 we might have class with many readonly parameters.
class ClassWithReadOnlyParameters
{
public readonly int $parameter1;
public readonly string $parameter2;
public readonly int $parameter3;
public readonly array $parameter4;
public readonly int $parameter5;
}
In PHP 8.2 we can declare whole class as readonly
. This way, all properties within the class will automatically inherit readonly
state.
readonly class ClassWhichIsReadOnly
{
public int $parameter1;
public string $parameter2;
public int $parameter3;
public array $parameter4;
public int $parameter5;
}
It is not possible to declare readonly
on specific PHP features:
However, it is possible to declare abstract and final classes as readonly
.
abstract readonly class AbstractClass {}
final readonly class FinalClass {}
Finally, it is possible to declare readonly
on a class that has no properties. In this case, child classes would be able to declare their readonly properties.
PHP 8.2 continues improving on type safety by introducing null
and false
as standalone types.
What does a standalone type means?
Previously, we were able to use these types as a part of the union, i.e. int|false
or string|false
.
function calculateNumberOrReturnFalse(): int|false
{
// ...business logic...
if ($calculationPossible) {
return $calculation; // return integer
}
return false;
}
Now, from PHP 8.2 it is possible to use null
and false
as standalone types.
For example:
function alwaysReturnTrue(): false
{
// ...business logic...
return false;
}
Of course, new standalone types are not intended as a replacement for proper error handling, throwing and catching errors. However, if cases of properly written functions with valid reason to always return true, false or null, it is possible to strictly type return parameter.
Finally, this change comes with a caveat. It is not possible to use true
and false
types in the union such as true|false
. This makes sense, because union of the true
and false
would be redundant feature because it is essentially a boolean
type.
Disjunctive Normal Form (DNF) is standardized way to organize boolean expressions. Meaning, with disjunctive normal form types it is possible to structure boolean expression with ANDs and ORs.
To understand this specific feature, let’s quickly refresh our knowledge on two PHP features:
PHP 8.0 introduced Union Types and this feature allows declaring property, parameter or return type with multiple types.
private int $commonSingleTypeVariable;
private int|float $twoTypesVariable;
PHP 8.1. introduced Intersection Types, as a feature that is somewhat opposite of union types. Intersection Types allows declaring that a specific value belongs to all of the declared classes or interface types.
public function exampleFunction(Interface1&Interface2 $bothStringAndIntValue)
{
// business logic
}
Now, PHP 8.2 introduced Disjunctive Normal Form Types (DNF) as a way to combine union types and intersection types. However, there is a strict rule to this. When combining union and intersection types, intersection types must be grouped within the brackets.
function disjunctiveNormalFormTypesExample((Interface1&Interface2)|null $wellTypedVariable) {
if (null === $wellTypedVariable) {
return $something; // or null for example
}
// ... otherwise continue with the business logic
}
The (Interface1&Interface2)|null $wellTypedVariable
represents DNF and it means: variable $wellTypedVariable must implement methods from both Interface1 and Interface2 or it can be a nullable value.
As of PHP 8.2 traits support constants.
trait ReusableCode
{
public const IMPORTANT_CONSTANT = 3600;
}
However, it is not possible to use constant within the trait itself. It is only possible to use this constant within the class using that trait.
For example:
trait ReusableCode
{
public const IMPORTANT_CONSTANT = 3600;
public function reusableFunction(int $importantVariable): string
{
// It is NOT allowed using constant within the trait
if ($importantVariable > ReusableCode::IMPORTANT_CONSTANT) {
return false;
}
}
}
// However once we use the trait within the class
class ExampleClass
{
use ReusableCode;
}
// It is allowed to use constant from the trait used within the class
ExampleClass::IMPORTANT_CONSTANT;
When our codebase ends up in the error state this generates back trace with all the details including values stored within the parameters. Commonly we forward these back traces to different services for keeping tracks of errors and warnings. These are services such as Sentry.io, Elastic, Datadog, Amazon CloudWatch etc.
While this might be ok for development and testing environments, in the production environments we are dealing with sensitive data. Very often we have to write different filtering mechanisms to make sure sensitive parameters and sensitive data are not exposed within the logs.
PHP 8.2 comes with a great feature to redact parameters in back traces. It is super simple. We can annotate sensitive attribute with #[\SensitiveParameter]
and this will indicate PHP to filter out this value in back traces. Sensitive parameter will be replaced with Object(SensitiveParameterValue))
.
Original RFC (see references) has a fantastic examples on how this is working.
Here is a basic use case:
function test(
string $foo,
#[\SensitiveParameter] string $privateKey,
string $baz
) {
throw new \Exception('Any Exception');
}
test('foo', 'sensitive_parameter-private_key', 'baz');
/*
Fatal error: Uncaught Exception: Error in example.php:10
Stack trace:
#0 test.php(...): test('foo', Object(SensitiveParameterValue), 'baz')
*/
As you can see, sensitive_parameter-private_key
will not be part of the back trace. Instead it will be replaced with Object(SensitiveParameterValue)
. Neat!
In short, PHP 8.2 introduces a new random number generator that solves many known problems. New random number generator is a single Randomizer
class that provides different randomization methods. New Randomizer
performs better and does not rely on global state.
For example:
$randomizer = new Random\Randomizer(); // default engine is Random\Engine\Secure()
$randomInt = $randomizer->getInt(); // replaces mt_rand()
$randomRangedInt = $randomizer->getInt(1,100); // replaces mt_rand() and random_int()
$randomBytes = $randomizer->getBytes(10); // replaces random_bytes()
$shuffledArray = $randomizer->shuffleArray(['1','2','3']); // replaces shuffle()
$shuffledString = $randomizer->shuffleString('abcdefg'); // replaces str_shuffle()
In short, PHP 8.2 allows the use of ->/?->
operator to fetch properties of enums in constant expressions. According to RFC for this feature, the primary motivation was to allow fetching the name and value properties in the places where it was not possible before, like in arrays.
For example:
// This is not possible before PHP 8.2
const C = [self::B->value => self::B];
Dynamic propertes in PHP allowed programmers to instantiate an object and to assign a property to that object that does not exist in the original class. We call this dynamically setting and retreiving undeclared class properties. From PHP 8.2 this will throw deprecation warnings and the plan is to deprecate it in PHP 9.0.
class Foo {
private string $bar;
}
$instance = new Foo();
// From PHP 8.2 this will throw a deprecation warning
// From PHP 9.0 this will throw an ErrorException
$instance->newProperty = 'anything';
PHP 8.2 provides a way to silence deprecation warnings for creation of dynamic properties by using #[AllowDynamicProperties] notation.
#[AllowDynamicProperties]
class Foo {
private string $bar;
}
$instance = new Foo();
// Will not throw a deprecation warning
$instance->newProperty = 'anything';
Up to PHP 8.1 there are callables that are accepted by the callable type, also by the is_callable()
function and call_user_func()
. However, they are not supported by $callable()
.
List of these callables
"self::method"
"parent::method"
"static::method"
["self", "method"]
["parent", "method"]
["static", "method"]
["Foo", "Bar::method"]
[new Foo, "Bar::method"]
However, to make things easier, most of the deprecated callables have a straightforward replacement, “self” is to be replaced with self::class
.
"self::method" -> self::class . "::method"
"parent::method" -> parent::class . "::method"
"static::method" -> static::class . "::method"
["self", "method"] -> [self::class, "method"]
["parent", "method"] -> [parent::class, "method"]
["static", "method"] -> [static::class, "method"]
utf8encode()
and utf8decode()
These two built-in PHP functions encoded “Latin 1” (ISO-8859-1) to “UTF-8” and decode UTF-8 to Latin. Functions are useful, but they have downsides. Firstly, their names suggest that any provided input can be converted to UTF-8 which is not correct. Secondly, functions lack error messages, meaning it is not easy to recognize incorrect usage.
RFC states that, based on the survey from April 2021 conducted on 1000 packages sorted by Packagist popularity, out of 37 packages using these functions only 4 libraries were clearly using them correctly.
Finally, RFC also suggests alternatives to removed functionality. Most interesting one is the most used one, for character conversion, stating that ext/mbstring
is probably one that is most commonly used.
ext/mbstring: $utf8 = mb_convert_encoding($latin1, 'UTF-8', 'ISO-8859-1'); and $latin1 = mb_convert_encoding($utf8, 'ISO-8859-1', 'UTF-8');
ext/intl: $utf8 = UConverter::transcode($latin1, 'UTF8', 'ISO-8859-1'); and $latin1 = UConverter::transcode($utf8, 'ISO-8859-1', 'UTF8');
ext/iconv: $utf8 = iconv('ISO-8859-1', 'UTF-8', $latin1); and $latin1 = iconv('UTF-8', 'ISO-8859-1', $utf8);
In the PHP programmers embed variables in strings with double-quotes and in heredoc. We call this string interpolation. There are several ways to accomplish string interpolation.
“$foo”
“{$foo}”
“${foo}”
“${expr}”
which is equivalent to (string) ${expr}
While options 1 and 2 to have their pros and cons, in the RFC describes options 3 and 4 as easily confusing due to overlapping syntax. Furthermore, option 3 is less capable than 1 and 2, and option 4 has completely different semantic.
Therefore, RFC proposed to deprecate options 3 and 4.
var_dump("${foo}");
// Deprecated: Using ${} in strings is deprecated
var_dump("${(foo)}");
// Deprecated: Using ${} (variable variables) in strings is deprecated
RFC also proposes a migration paths, as the easiest way to implement this change.
Migrating “braces after the dollar sign” to “braces outside the variable”, option 3 to option 2.
Straightforward, simply by moving the dollar sign inside the braces.
"${foo}" => "{$foo}"
"${foo[expr]}" => "{$foo[expr]}"
Migrating “variable variables” to “braces outside the variable”, option 4 to option 2.
Also straightforward, simply adding a braces around the whole interpolation.
"${foo->bar}" => "{${foo->bar}}"
PHP 8.2 deprecated the use of QPrint, Base64, Uuencode, and HTML-ENTITIES ‘text encodings’ for all mbstring functions.
This deprecation is happening for several reasons:
Using mbstring functions with deprecated values will result in some of the following warnings
Deprecated: mb_check_encoding(): Handling HTML entities via mbstring is deprecated; use htmlspecialchars, htmlentities, or mb_encode_numericentity/mb_decode_numericentity instead...
Deprecated: mb_strcut(): Handling HTML entities via mbstring is deprecated; use htmlspecialchars, htmlentities, or mb_encode_numericentity/mb_decode_numericentity instead...
Deprecated: mb_convert_encoding(): Handling QPrint via mbstring is deprecated; use quoted_printable_encode/quoted_printable_decode instead...
Deprecated: mb_convert_encoding(): Handling Base64 via mbstring is deprecated; use base64_encode/base64_decode instead in...
Deprecated: mb_convert_encoding(): Handling HTML entities via mbstring is deprecated; use htmlspecialchars, htmlentities, or mb_encode_numericentity/mb_decode_numericentity instead...
Deprecated: mb_strlen(): Handling Base64 via mbstring is deprecated; use base64_encode/base64_decode instead...
Deprecated: mb_strlen(): Handling HTML entities via mbstring is deprecated; use htmlspecialchars, htmlentities, or mb_encode_numericentity/mb_decode_numericentity instead...
PHP 8.2 is released on 8th December. It comes with many nice features such as readonly classes, traits supporting constants, redacting parameters from back traces etc. As in the previous versions PHP is aiming to cleanup legacy issues and to standardize coding approaches which we can see by deprecated features such as dynamic properties, partially supported callables and streamlining string interpolation. PHP 8.2 also improves security and performances, for example by introducing new random number generator.
null
,true
or false
This article is about the code review best practices. It explains code review from the… Read More
API design is an important aspect of modern software development. It allows different systems to… Read More
This article sheds some light related to the question will ChatGPT or AIs in general… Read More
This article is about Automation and Artificial Intelligence in Software Engineering: Experiences, Challenges, and Opportunities.… Read More
PHP is getting more and more features. Enumerations in PHP are one of the latest… Read More
This article writes about what is new in PHP 8.0. These are personal notes related… Read More