MMS • Deepak Vohra
Article originally posted on InfoQ. Visit InfoQ
Key Takeaways
- PHP 8 supports array unpacking with string keys, introduces a new function to determine if an array is a list, and a stable array sort function.
- Exception handling adds support for non-capturing catches, and use of the throw statement where only an expression is usable.
- New features related to variable declarations are explicit octal notation 0o/0O for integer literals, limited $GLOBALS usage, and namespaced names as single tokens.
- New operators include an improved non-strict string to number comparison, the new null-safe operator, locale-independent float to string cast, and stricter type checks for arithmetic/bitwise operators. Support has been added to fetch properties of enums in const expressions with the ->/?-> operators.
- Some other improvements are made in traits, including validation of abstract trait methods, and support for constants in traits.
This article is part of the article series “PHP 8.x”. You can subscribe to receive notifications about new articles in this series via RSS. PHP continues to be one of the most widely used scripting languages on the web with 77.3% of all the websites whose server-side programming language is known using it according to w3tech. PHP 8 brings many new features and other improvements, which we shall explore in this article series. |
In this last installment of our PHP 8 article series, we are going to cover support for several new features and improvements to arrays, variables, operators, exception handling, traits, and more.
Arrays and Strings
Deprecate autovivification on false
Autovivification is the automatic creation of new arrays when undefined elements of an array are referenced, for example:
<?php
$arr['a'][1] = 'a';
var_dump($arr);
The new array $arr is created automatically even though it does not exist before it is referenced. The output is:
array(1) { ["a"]=> array(1) { [1]=> string(1) "a" } }
Autovivification allows a developer to refer to a structured variable such as an array, as well as to its subelements, without first explicitly creating the structured variable.
PHP 8.0 supports autovivification from undefined variables, null values, and false values. Autovivification from null is demonstrated by the following script:
<?php
$arr = null;
$arr[] = 1;
var_dump($arr);
The output is:
array(1) { [0]=> int(1) }
Autovivification from undefined variable is demonstrated by this script:
<?php
$arr[] = 'undefined value';
$arr['variableNotExist'][] = 1;
var_dump($arr);
The output from the script is as follows:
array(2) { [0]=> string(15) "undefined value" ["variableNotExist"]=> array(1) { [0]=> int(1) } }
PHP 8.0 even allows autovivification from false
. PHP 8.0 does not however allow autovivification from from scalar values, as demonstrated by the following script:
<?php
$arr = 1;
$arr[] = 1;
var_dump($arr);
The script output is:
Uncaught Error: Cannot use a scalar value as an array
With PHP 8.1, autovivification is supported only for undefined variables and null
values. Autovivification from false
is deprecated in 8.1. To demonstrate, run the following script:
<?php
$arr = false;
$arr[] = 1;
The output from the script is as follows:
Deprecated: Automatic conversion of false to array is deprecated
Array unpacking with string keys
Unpacking is used in the context of an array’s elements to itemize or unpack the array elements using the unpacking operator …. Unpacking of string keys in an array is not allowed in PHP 8.0, as is unpacking of a function’s arguments. PHP 8.1 allowed unpacking a function’s arguments with the introduction of named arguments; named arguments may be used after unpacking the arguments with the condition that a named argument must not override already unpacked arguments. An example of unpacking a function’s named arguments was demonstrated in the article “PHP 8 – New Features for Functions and Methods”.
PHP 8.1 additionally allows unpacking of string keys into an array using the … operator, as demonstrated by the example:
1];
$array2 = ["two" => 2];
$array1 = ["one" => "one"];
$array2 = ["two" => "two"];
$array = ["one" => 0, "two" => 10, ...$array1, ...$array2];
var_dump($array);
$array1
and array2
get unpacked with the … operator, with the latter array’s keys overriding the former’s. The output is as follows:
array(2) { ["one"]=> string(3) "one" ["two"]=> string(3) "two" }
The array_merge()
function is used when unpacking arrays behind the scenes, so unpacking the two arrays in the preceding example is the same as calling array_merge($array1, $array2)
.
The new feature affects only string keys, while integer keys are renumbered; original integer keys are not retained. An example of using integer keys is the following:
1];
$array2 = [2 => 2];
$array1 = [1 => "one"];
$array2 = [2 => "two"];
$array = [1 => 0, 2 => 10, ...$array1, ...$array2];
var_dump($array);
Another example of unpacking arrays with string keys is presented below, where quoted integer keys are actually used as integer keys. String keys containing integers are cast to integer types; as an example:
1];
$array2 = ["2" => 2];
$array1 = ["1" => "one"];
$array2 = ["2" => "two"];
$array = ["one" => 0, "two" => 10, ...$array1, ...$array2];
var_dump($array);
Output is as follows:
array(4) { ["one"]=> int(0) ["two"]=> int(10) [0]=> string(3) "one" [1]=> string(3) "two" }
A new function to find if an array is a list
An array type supports both string and integer keys. It may be useful to know if an array’s keys are actually numbered 0 … count($array)-1
, in which case we call it a list. The new function array_is_list(array $array): bool
does just that by returning a bool
value true
if it is a list and false if it is not. The following example demonstrates the new function:
'a', 0 => 'b', 2=>'c'];
$y = [0 => 'a', 1 => 'b', 2=>'c'];
var_export(array_is_list($x));
var_export(array_is_list($y));
The output is as follows:
false
true
Sorting of arrays made stable
In PHP 7 the array sorting operation is unstable. “Unstable” implies that the order of “equal” elements is not guaranteed in successive sorts. PHP 8.0 makes sorting stable. If multiple elements in an input array compare equal, they are always sorted adjacently. In other words, equal elements retain the order they have in the original array. Stable sorts are especially useful in sort operations involving complex data when a sort is made on a specific attribute of the data. In such cases, inconsistent output may result if sorting is unstable. The following script demonstrates a stable sort:
'c',
'c' => 'c',
'b' => 'a',
'a' => 'a',
];
asort($array);
foreach ($array as $key => $val) {
echo "array[" . $key . "] = " . $val . "n";
}
The result with a stable sort is always:
array[b] = a array[a] = a array[d] = c array[c] = c
Deprecate ${} string interpolation
Embedding variables in strings with double-quotes (“) has been allowed in various forms. PHP 8.2 deprecates two such forms of string interpolation: ${var}
and ${expr}
. The ${var}
form has an overlapping syntax with two other forms ("$var")
and ("{$var}")
and is less capable than the other forms. The ${expr}
, which is the equivalent to (string)
${expr})
is rarely used.
The following example allows some string interpolations while disallowing the ${var}
and ${expr}
forms:
<?php
$str = 'hello';
var_dump("$str");
var_dump("{$str}");
var_dump("${str}");
var_dump("${str}");
var_dump("${(str)}");
The output is as follows:
Deprecated: Using ${var} in strings is deprecated, use {$var} instead in /home/deepakvohra/php-src/scripts/php-info.php on line 8
Deprecated: Using ${var} in strings is deprecated, use {$var} instead in /home/deepakvohra/php-src/scripts/php-info.php on line 10
Deprecated: Using ${expr} (variable variables) in strings is deprecated, use {${expr}} instead in /home/deepakvohra/php-src/scripts/php-info.php on line 12
string(5) "hello" string(5) "hello" string(5) "hello" string(5) "hello"
Fatal error: Uncaught Error: Undefined constant "str"
Exception Handling
PHP 8.0 introduces non-capturing catches. Previously, each catch statement had to declare a variable for the exception being caught. As, an example the following script declares the variable $exc of type Exception in the catch statement:
<?php
function sortArray(array $arrayToSort)
{
try {
if (count($arrayToSort) === 0) {
throw new Exception("Array is empty; please provide a non-empty array");
}
}
catch (Exception $exc) {
echo $exc;
}
}
$arrayToSort = array();
The script outputs an exception message when run:
Exception: Array is empty; please provide a non-empty array
Though the $exc variable was used in the preceding example, exception variables are not always used. With non-capturing catches exception variables are made optional; they don’t have to be declared, and therefore cannot be used, as in the script:
<?php
function sortArray(array $arrayToSort)
{
try {
if (count($arrayToSort) === 0) {
throw new Exception("Array is empty; please provide a non-empty array");
}
}
catch (Exception) {
}
}
A multi-catch catch statement may also not capture the exceptions as in the example:
<?php
class Exception1 extends Exception
{
}
class Exception2 extends Exception
{
}
class A
{
public function fn1()
{
try {
throw new Exception1();
}
catch (Exception1 | Exception2) {
}
}
}
The throw expression
The throw
statement could not previously be used in instances where expressions are allowed. PHP 8.0
adds support for using throw
with expressions. As an example, the default case in the match
expression in the following script makes use of the throw
expression.
push('a');
try {
match ('set') {
'push' => $vector->push('b'),
'pop' => $vector->pop(),
default => throw new Exception()
};
}
catch (Exception $exc) {
echo $exc;
}
print_r($vector);
The output is as follows:
Exception in C:php-8.1.9-nts-Win32-vs16-x64scriptssample.php:11 Stack trace: #0 {main}DsVector Object ( [0] => a )
An example of using throw with a null-coalescing operator (??) is as follows:
<?php
$name = $_GET['name'] ?? throw new Exception("Please provide 'name' as a request parameter");
echo "Hello " . htmlspecialchars($name)."
";
Output is as follows:
Uncaught Exception: Please provide 'name' as a request parameter
As another example, throw here is used with the ternary operator (?):
<?php
try {
function fn1(bool $param1)
{
$value = $param1 ? true: throw new InvalidArgumentException();
}
fn1(true);
fn1(false);
}
catch (Exception $exc) {
echo $exc;
}
Output is as follows:
InvalidArgumentException in C:php-8.1.9-nts-Win32-vs16-x64scriptssample.php:5 Stack trace: #0 C:php-8.1.9-nts-Win32-vs16-x64scriptssample.php(9): fn1(false) #1 {main}
Variables
Explicit octal integer literal notation
A literal octal notation could produce misleading results such as 12 === 012
evaluating to false
. PHP 8.1 adds support for explicit octal notation 0o/0O
for integer literals, similar to the 0x/0X
notation for hexadecimal and the 0b/0B
notation for binary. The following script demonstrates the explicit octal notation:
<?php
var_export(0o12 === 10);
var_export(0o26 === 22);
var_export(0O12 === 10);
var_export(0O26 === 22);
var_export(012 === 0o12);
var_export(012 === 0O12);
Output is as follows:
true
true
true
true
true
true
Restrict $GLOBALS usage
The usage of the $GLOBALS
variable, which provides direct access to the internal symbol table, has been restricted to disallow improper usage. As of PHP 8.1, $GLOBALS
can only be modified using the $GLOBALS[$name] = $value
syntax. To demonstrate, run the following script that directly accesses $GLOBALS
:
<?php
$x=1;
$GLOBALS = 1;
$GLOBALS += 1;
$GLOBALS =& $x;
$x =& $GLOBALS;
unset($GLOBALS);
An error message is output:
$GLOBALS can only be modified using the $GLOBALS[$name] = $value syntax
The following use of $GLOBALS
is OK:
<?php
$GLOBALS['a'] = 1;
$GLOBALS['a']--;
var_dump($GLOBALS['a']);
Output is:
int(0)
Treat namespaced names as single token
To allow reserved keywords to appear within namespaced names, PHP 8.0 treats namespaced names as a single token. This reduces the chance of introducing backward incompatibility due to new reserved words that could already have been used in a namespace name.
To demonstrate this, the following script uses reserved word fn in a namespace name. The script also makes use of the ReflectionClass
to output class attributes such as whether it is a namespace, the class name, the namespace, and the class methods:
inNamespace());
var_dump($class->getName());
var_dump($class->getNamespaceName());
$methods = $class->getMethods();
var_dump($methods);
The output is as follows:
bool(true) string(18)
"dowhilefniterC" string(16)
"dowhilefniter"
array(1) { [0]=> object(ReflectionMethod)#2 (2) { ["name"]=> string(3) "fn2" ["class"]=> string(18) "dowhilefniterC" } }
Operators
PHP 8 adds quite a few new operator related features.
Non-strict string to number comparisons made more useful
Before PHP 8.0, non-strict string to number comparisons make an assumption that the string is actually a number, therefore convert the string to a number and make a number comparison. PHP 8.0 ensures that the string is a number before converting types and making a number comparison. Otherwise the number is converted to a string and a string comparison is made instead. The new feature does not apply to strict comparison operators === and !== that require both operands to be of the same type and do not perform implicit type conversion. Only the non-strict comparison operators ==, !=, >, >=, <, <= and ⇔ are affected. The following script demonstrates the new string to number comparison:
<?php
var_dump(1 == "001");
var_dump(1 == "1.0");
var_dump(1.0 == "+1.0E0");
var_dump(1 == "1 ");
var_dump(1 == " 1");
var_dump(1 == " 1 ");
var_dump("one" == "1");
var_dump("one" != "1");
The output is as follows:
bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(false) bool(true)
Nullsafe operator
How often have you called a method, or fetched a property on the result of an expression assuming that the result is non-null. As the result could be null, it is best to first ensure that it is not null. You could use explicit if(result!=null)
comparisons but it could involve hierarchical, multiple comparisons. The following script makes use of the traditional if
comparisons to make null-safe comparisons for addition of integer values:
i = 0;
}
function addA(Sum $sum,int $a):?Sum
{
$sum->i= $a+$sum->i;
return $sum;
}
function addB(Sum $sum,int $b):?Sum
{
$sum->i= $b+$sum->i;
return $sum;
}
function addC(Sum $sum,int $c):?Sum
{
$sum->i= $c+$sum->i;
return $sum;
}
function getSum(Sum $sum):int
{
return $sum->i;
}
}
$a = new Sum();
if ($a->addA($a,1) !== null) {
if ($a->addB($a,2) !== null) {
if ($a->addC($a,3) !== null) {
echo $a->getSum($a);
}
}
}
The result is 6.
The new operator ?->
could be used to link calls to make null-safe comparisons, and when the left operand to the operator evaluates to null, it stops all subsequent comparisons. The following script demonstrates linking operands to make null-safe comparisons using the new ?->
operator for the same addition.
echo $a->addA($a,1)?->addB($a,2)?->addC($a,3)?->getSum($a);
Locale-independent float to string cast
Float to string casts before PHP 8.0 are locale dependent, that is, the decimal separator varies between locales. This could lead to several inconsistencies such as interpreting strings as being malformed, or interpreting strings as numerical values. A consistent string representation for floats was much needed, and PHP 8.0 provides just that. The following script demonstrates local-independent float-to-string casts.
<?php
setlocale(LC_ALL, "de_DE");
$f = 1.23;
echo (string) $f;
echo strval($f);
The result is:
1.23
1.23
Stricter type checks for arithmetic/bitwise operators
Arithmetic/bitwise operators, which are +, -, *, /, **, %, <>, &, |, ^, ~, ++, –, must only be applied to operands that support them. An operand that is an array, resource, or non-overloaded object cannot be used with these operators. PHP 8.0 makes a strict type check and throws a TypeError
if the operands are not compatible with the arithmetic/bitwise operators. To demonstrate this, the following script uses the subtraction operator (-) with arrays:
<?php
$arrayToSort = array(3, 1, 0, 2);
var_dump($arrayToSort - [1]);
Result is an error:
Uncaught TypeError: Unsupported operand types: array - array
Fetch properties of enums in const expressions with the ->/?-> operators
Enum objects aren’t allowed in constant expressions such as array keys, which wouldn’t support the use case in which you require to fetch the name and value properties of enums in constant expressions. To demonstrate, run the following script with version 8.1:
name => self::ASC->value];
}
An error message is generated:
Constant expression contains invalid operations
PHP 8.2 allows the -> ?->
operators to fetch properties of enums in constant expressions, as in the following script:
name => self::ASC->value];
}
function get()
{
static $g = Sort::ASC->value;
}
#[Attr(Sort::ASC->name)]
class SortClass
{
}
function set($s = Sort::ASC->value,)
{
}
class SortClass2
{
public string $n = Sort::ASC->name;
}
// The rhs of -> allows other constant expressions
const DESC = 'DESC';
class SortClass3
{
const C = Sort::ASC->{DESC};
}
Traits
Validation of abstract trait methods
PHP 8.0 validates abstract trait methods in the composing/using class to ensure that their signatures match. The implementing method must be compatible with the trait method, with compatibility being defined as signature compatibility:
- Arity compatibility; the number of function arguments must be the same
- Contravariant parameter type compatibility
- Covariant return type compatibility
Additionally, the static methods must be kept static. Abstract private trait methods are allowed. The following script demonstrates an accurate implementation of abstract trait methods:
hello());
}
}
class A {
use HelloTrait;
private function hello(): string {return "Hello John"; }
}
To demonstrate incompatibility, change the implemented method as follows:
private function hello(): stdClass { }
In this case, an error condition is indicated:
Declaration of A::hello(): stdClass must be compatible with HelloTrait::hello(): string
A non-static method in the trait cannot be made static in the class. To demonstrate, change the implementation as in:
private static function hello(): string { }
An error condition is indicated:
Cannot make non static method HelloTrait::hello() //static in class A
Calling a static element on a Trait is deprecated
PHP 8.1.0 deprecates calling a static element on a trait, which implies that calling a static method, or a static property, directly on a trait is deprecated. Static methods and properties should only be accessed on a class using the trait. The following script demonstrates the deprecation:
<?php
trait HelloTrait {
public static $a = 'static property in trait';
public static function hello(): string {return "Hello";}
}
class A {
use HelloTrait;
}
echo A::$a;
echo A::hello();
echo HelloTrait::$a;
echo HelloTrait::hello();
Output is as follows:
Deprecated: Accessing static trait property HelloTrait::$a is deprecated, it should only be accessed on a class using the trait
Deprecated: Calling static trait method HelloTrait::hello is deprecated, it should only be called on a class using the trait
Constants in Traits
Invariants, also called constants, are not allowed in a trait in PHP 8.1. PHP 8.2 adds support for constants in traits. The constants may be used by the trait’s methods or in the composing class. To demonstrate the usefulness of constants in traits consider the following example in which a constant called MAX_ARRAY_SIZE
is declared in the composing class:
self::MAX_ARRAY_SIZE) {
throw new Exception("array size out of range");
} else {
sort($arrayToSort);
foreach ($arrayToSort as $key => $val) {
echo "$key = $val ";
}
echo "
";
}
}
}
class SortClass
{
private const MAX_ARRAY_SIZE = 10;
use SortTrait;
}
$arrayToSort = ["B", "A", "f", "C", 1, "a", "F", "B", "b", "d"];
$obj = new SortClass();
$obj->sortArray($arrayToSort);
The script runs ok to generate the following output:
0 = 1 1 = A 2 = B 3 = B 4 = C 5 = F 6 = a 7 = b 8 = d 9 = f
The following 8.2 version of the same script declares the constant MAX_ARRAY_SIZE
in the trait itself.
self::MAX_ARRAY_SIZE) {
throw new Exception("array size out of range");
} else {
sort($arrayToSort);
foreach ($arrayToSort as $key => $val) {
echo "$key = $val ";
}
echo "
";
}
}
}
class SortClass
{
use SortTrait;
}
$arrayToSort = ["B", "A", "f", "C", 1, "a", "F", "B", "b", "d"];
$obj = new SortClass();
$obj->sortArray($arrayToSort);
The output is the same:
0 = 1 1 = A 2 = B 3 = B 4 = C 5 = F 6 = a 7 = b 8 = d 9 = f
As another example, the following script declares three constants in a trait and makes use of them in the trait itself:
getSortType("ASCending");
The output is:
Sort type is ASC
A trait constant cannot be accessed directly with syntax TRAIT_NAME::CONSTANT
, which the following script does:
getSortType("ASCending");
An error is shown when the script is run:
Uncaught Error: Cannot access trait constant SortTrait::SORT_TYPE_1 directly
Using $this is ok, as in:
if (str_contains($sortType, $this::SORT_TYPE_1)) {
echo 'Sort type is ASC';
}
Trait constants can be declared as final for class constants. Compatibility restrictions that apply to a trait’s properties also apply to its constants.
Enumerations can use traits having constants and the constants are equivalent to defining them in the enumeration directly, as in the script:
<?php
trait SortTrait
{
private const SortType = "ASC";
}
enum Enum1: int
{
use SortTrait;
case CaseA = self::SortType;
}
Phasing out Serializable
PHP 7.4 introduced the custom serializable mechanism with two new magic methods called __serialize(): array
, and __unserialize(array $data): void
. The __serialize()
method returns an array with all the necessary state of the object, and the __unserialize()
method restores the object state from the given data array. The new custom serializable mechanism is designed to phase out the Serializable
interface. PHP 8.1 generates a deprecation warning if a non-abstract class implements Serializable
but does not implement __serialize()
and __unserialize()
. Such a class is called “only Serializable”. To demonstrate this, run the script:
<?php
class A implements Serializable {}
A deprecation message is generated:
Deprecated: A implements the Serializable interface, which is deprecated. Implement __serialize() and __unserialize() instead (or in addition, if support for old PHP versions is necessary)
Fatal error: Class A contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Serializable::serialize, Serializable::unserialize)
Deprecate dynamic properties
A dynamic class property is one that is referred to before being declared. A dynamic property is created automatically. Dynamic properties are deprecated in PHP 8.2. The main reason for deprecating dynamic properties is to avoid the ambiguity resulting from whether a user mistyped a property name, or rather wants to create a new property. To demonstrate, run the following script that creates a dynamic property in PHP 8.2:
name = "John";
$a->firstname = "John";
A deprecation message is generated:
Deprecated: Creation of dynamic property A::$firstname is deprecated
If you still want dynamic properties implement the magic methods __get/__set
, or use the new attribute #[AllowDynamicProperties]
. The pre-packaged class stdClass
is already marked with the #[AllowDynamicProperties]
attribute.
Deprecate passing null to non-nullable arguments of built-in functions
User-defined functions don’t accept null for non-nullable arguments when coercive typing mode is set (strict_types=1)
. With PHP 8.1, even built-in functions won’t accept null for non-nullable arguments, generating a Deprecation notice as demonstrated by script:
<?php
$var=null;
strlen($var);
Output is:
Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated
In PHP 9.0 a TypeError
the deprecation notice will be replaced through an error.
In this article, we discussed new features in PHP 8 related to arrays, variables, operators, and exception handling. We also discussed some trait-, class-, and function-related features. This article concludes the PHP 8 article series.
This article is part of the article series “PHP 8.x”. You can subscribe to receive notifications about new articles in this series via RSS. PHP continues to be one of the most widely used scripting languages on the web with 77.3% of all the websites whose server-side programming language is known using it according to w3tech. PHP 8 brings many new features and other improvements, which we shall explore in this article series. |