Article: PHP 8 – Arrays, Variables, Operators, Exception Handling and More

MMS Founder
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.

About the Author

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.