Key Takeaways
- PHP 8 is a major update to PHP that introduces several new features and performance optimizations.
- Attributes in PHP 8 are configuration directives used to add syntactic metadata applied to individual classes, functions, methods, and other constructs.
- The new
match
expression in PHP 8 is a control flow structure that matches a given subject expression with one or more alternative conditional expressions using identity comparison and returns a value from the matched branch. - PHP 8.1 adds support for using the
new
operator for parameter default values, attribute arguments, static variable initializers, and global constant initializers. - PHP 8 adds support for using the
instanceof
operator with arbitrary expressions.
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. |
PHP 8 is a major update to PHP that introduces several new features and performance optimizations, including attributes, match expression, instanceof
operator, new operator, a new JIT compiler, and more.
Attributes provide a way to add metadata to classes, methods, functions, parameters, properties, and class constants. Attributes are similar to annotations that are supported by some other languages. The new
operator, only available starting with PHP 8.1, can be used in initializers for default parameter values, static variables, and attribute arguments. The match
expression is a new control structure that evaluates a subject expression with multiply-branched conditional expressions using the identity operator and executes the matched branch. The instanceof
operator, which previously could only be used with a class object, can also be used with any arbitrary expression . The JIT (Just-In-Time) compiler brings performance and usability improvements.
Some of the examples are based on data structures requiring to download and install the php_ds
extension from here. On Windows, download the DLL for 8.1 Non Thread Safe (NTS) x64 from here, and extract the php_ds-1.4.0-8.1-nts-vs16-x64.zip file to a directory. Copy the php_ds.dll from the extracted directory to the .\ext directory within the PHP 8.x installation root, for example C:\php-8.1.9-nts-Win32-vs16-x64\ext. Add the following line to the php.ini configuration file:
extension=php_ds
Restart the PHP built-in server if it is already running.
Attributes
Attributes are configuration directives used to annotate or decorate classes, methods, functions, parameters, properties, and class constants with structured and machine-readable metadata. “Structured” means the metadata information may be read and parsed, making attributes different from unstructured doc-comments, which are just plain strings. Attributes can be used to provide configuration and other information that is relevant only some of the time, and therefore embedded, not hard-coded into the PHP script. The following is the typical sequence for using attributes:
- Declare an attribute class. An attribute class is a regular PHP class preceded by
#[Attribute]
on a line by itself. The class may optionally declare a constructor. An attribute is identified by its class; the class name is the attribute name when the attribute is used. - Apply, or use the attribute on classes, methods, functions, parameters, properties, and class constants declarations. As an example, if the attribute class is called
Validate
, the attribute is used as#[Validate]
. The same attribute may be used multiple times on different declarations. And multiple attributes may be applied to the same declaration. - Get or read the attributes, if needed, using the Reflection APIs at runtime.
Attributes can have several uses, such as:
- Providingn alternative to an interface, the benefit being that whereas a class implementing an interface requires all methods from the interface to be implemented, an attribute may be used only when needed, thus avoiding unnecessary method implementations.
- Altering compilation, diagnostics, code generation, and runtime behavior.
- Separating PHP engine and extensions. PHP core, and extensions, could have some attributes on some of the declarations.
- Embedding configuration information specific to a declaration. As an example, attributes on a method may indicate which events the method listens to.
- Migrating from docblocks to attributes.
By default, an attribute can be used on any or all of the supported declaration types. But an attribute use could be limited to one or more types of declarations using selected bitmask flags Attribute::TARGET_CLASS, Attribute::TARGET_FUNCTION, Attribute::TARGET_METHOD, Attribute::TARGET_PROPERTY, Attribute::TARGET_CLASS_CONSTANT. Attribute::TARGET_PARAMETER, Attribute::TARGET_ALL
. The Attribute::TARGET_ALL
flag is the default. An attribute can be used multiple times on a single declaration using the bitmask flag Attribute::IS_REPEATABLE.
Next, we shall explore the use of attributes with an example. Consider that you want to sort an array using one or more sort functions, such as sort()
for sorting in ascending order, rsort()
for sorting in descending order, and shuffle()
for shuffling in random order. You may also want to validate the input array to verify that it is not an empty array, or that it has at least two elements to make sorting relevant. The sort type information and the validation information could be provided in the form of attributes.
First, declare an attribute class for validation.
#[Attribute]
class Validate {}
Then, declare another attribute class for the sort type. The bitmask flags Attribute::TARGET_CLASS
, and Attribute::IS_REPEATABLE
indicate that the attribute is only meant to be used with a class declaration, and that the attribute is repeatable.
#[Attribute(Attribute::TARGET_CLASS|Attribute::IS_REPEATABLE)]
class SortType {
function __construct($sortType){
$this->sortType = $sortType;
}
}
Declare now an interface with a single method called sortArray().
interface Sort
{
public function sortArray();
}
Finally, declare a class that implements the Sort interface.
class SortImpl implements Sort
{
…}
Within the class, declare two class properties, one for the sort type, and the second for the array to be sorted.
public string $sortType="";
public $arrayToSort=array("B", "A", "f", "C");
Declare a method to validate that the array to be sorted is not an empty array. Annotate the method with the #[Validate] attribute. Applying the attribute to the method makes the method discoverable using the Reflection APIs.
#[Validate]
public function arrayEmpty()
{
if (count($this->arrayToSort) === 0) {
throw new RuntimeException("Array is empty; please provide a non-empty array");
}
}
Declare a second method for validating that the array has at least two elements, and apply the #[Validate]
attribute to it.
#[Validate]
public function arraySize()
{
if (sizeof($this->arrayToSort) < 2) {
throw new RuntimeException("Please provide an array of size 2 or more");
}
}
Implement the sortArray
function. Based on the value of the SortType
, make an ascending/descending/shuffle sort.
public function sortArray()
{
if ($this->sortType == "asc") {
…
} elseif ($this->sortType == "desc") {
…
} else {
…
}
Add a function called performSort(Sort $sort)
to perform the sort. In the function, apply the validations before performing the sort.
Declare a class and apply the #[SortType]
attribute to it. As the attribute is repeatable, apply the attribute multiple times to perform different types of sort.
#[SortType(sortType: "desc")]
#[SortType(sortType: "shuffle")]
#[SortType(sortType: "asc")]
class QSort
{
}
Finally, create an instance of the class SortImpl
.
$sort = new SortImpl();
Get the class attributes using the Reflection API.
$ref = new ReflectionClass(QSort::class);
$attrs = $ref->getAttributes();
Iterate over the attributes array to call the performSort() function to perform the sort for each type of sort type, and output the result of the sort.
foreach ($attrs as $attr) {
…
}
The complete sort.php
script to demonstrate the use of attributes is available on GitHub. Copy the sort.php script to the scripts
folder, and with the PHP built-in server running and listening at http://localhost:8000 call the sort.php
script in a browser with url http://localhost:8000/scripts/sort.php. The result for the sort using the different sort types is displayed in the browser as shown in Figure 1.
Figure 1. Result of sorting using attributes
Because the shuffle sort is a random sort the result for shuffle is likely to be different each time the script is run, as shown in Figure 2.
Figure 2. Shuffle sort produces a different result
Because the sample array to sort has 4 elements, none of the validations fails. If the sample array is empty, on the contrary, the same script, with only the sample array changed, generates a runtime exception message : Uncaught RuntimeException: Array is empty; please provide a non-empty array
. Similarly, if the sample array has only 1 element a runtime exception is generated with message : Uncaught RuntimeException: Please provide an array of size 2 or more.
The enhanced new
operator
The new
operator is used to create an instance of a class. As of PHP 8.1, the new operator can be used in arbitrary expressions with the following syntax in which the expression must be wrapped in parentheses.
new (expression)(...$args)
The new
operator can be used in initializers for parameter default values, static variable initializers, global constant initializers, and attribute arguments as explored next with examples.
The new
operator in function parameter default values
To demonstrate the use of the new operator in function parameter default values, we shall use a variation of the array sort example. Consider a class called SortArray
that declares a method sortArray($arrayToSort)
to sort an array in ascending order.
class SortArray {
public function sortArray($arrayToSort) {
…
}
}
A second class called ReverseSortArray
declares a method to sort an array in descending order.
class ReverseSortArray {
public function sortArray($arrayToSort) {
…
}
}
Declare now a function that accepts any arbitrary array and an array sorter to sort the array. The default array sorter could be defined using the new operator to create an instance of the SortArray
.
function sortAnyArray($arrayToSort, $arraySorter = new SortArray)
{
return $arraySorter->sortArray($arrayToSort);
}
The complete script is available on GitHub. Run the sample script on the built-in server with url http://localhost:8000/scripts/sample.php
The output is shown here:
arrayToSort[0] = A arrayToSort[1] = B arrayToSort[2] = C arrayToSort[3] = f
arrayToSort[0] = f arrayToSort[1] = C arrayToSort[2] = B arrayToSort[3] = A
The new
operator in variable and constant initializers
The new
operator may be used in static variable initializers and global constant initializers. The new
operator is not supported, though, in static and non-static class property initializers, nor in class constant initializers. The following script demonstrates which variable and constant initializers are supported, and which aren't.
<?php
class A{ }
class B{
//static $a = new A; //New expressions are not supported in this context
//public $a = new A; //New expressions are not supported in this context
//const C = new A; //New expressions are not supported in this context
}
static $a = new A;
const C = new A;
The new
operator in Attribute Arguments
As you would expect, the new
operator can be used in attribute arguments, too. We shall use the same example we used to demonstrate the Attributes feature with one difference. Remember that we used the #[SortType]
attribute to annotate the QSort
class with the attribute arguments being passed to it as strings.
#[SortType(sortType: "desc")]
#[SortType(sortType: "shuffle")]
#[SortType(sortType: "asc")]
class QSort
{
}
For this case, declare a class called Str
that accepts a string argument as a constructor argument.
class Str{
function __construct($str){
$this->value = $str;
}
function __toString(){
return $this->value;
}
}
Use the new
operator in the attribute arguments as follows.
#[SortType(sortType: new Str("desc"))]
#[SortType(sortType: new Str("shuffle"))]
#[SortType(sortType: new Str("asc"))]
class QSort
{
}
Run the script in the built-in server with the same output as before, as shown in Figure 3.
Figure 3. The result from using new in attribute arguments
The new
operator is not allowed in some contexts
Earlier we mentioned some contexts in which the new
operator is not supported, that is static and non-static class property initializers, and class constant initializers. Additionally, the new
operator is not supported in the following contexts:
- Non-string dynamic class name
- Argument unpacking in constant expression
- Anonymous class in constant expression
- Unsupported constant expression
The following script generates an error for the statements commented out:
<?php
$list = [4, 6];
function fn1(
$a1 = new ('some_dynamic_class')(),
// $a2 = new (some_dynamic_class)(), // non string dynamic class - Cannot use dynamic class name in constant expression
// $b = new class {}, // Cannot use anonymous class in constant expression
// $c = new C(...$list), // Argument unpacking in constant expressions is not supported
// $d = new D($x), // Constant expression contains invalid operation
) {}
The new match
expression
PHP 8 introduces a new match
expression as a control flow structure that matches a given subject expression with one or more alternative branches using identity comparison, and returns a value that is the result from the matched branch. The match expression is similar to a switch
statement but different as follows:
- The
match
expression uses the identity operator(===)
for comparison, whereasswitch
uses the equality(==)
operator. - The
match
expression returns a value, whereas aswitch
doesn't. The return value may be assigned to a variable. - The
match
expression breaks automatically out of thematch
after one of the branches is matched. Theswitch
requires abreak
; statement. - The
match
expression does not fall-through, as a switch does in the absence of break; statement/s. - The
match
expression supports multiple conditions separated by a comma (,) in the same branch, whereasswitch
doesn't. - The
match
expression must be exhaustive, which implies that it must handle all values of the subject expression.
Next, we demonstrate the use of the match
expression with some examples.
Outputting the Return value
To start off with a simple example, the match
expression in the following script matches the integer value of 1
, given as the subject expression, with multiple conditional expressions including the default
pattern.
<?php
echo match (1) {
0 => 'A',
1 => 'B',
2 => 'C',
default => 'Default',
};
Run the script in the built-in engine by calling the script in a browser with url
http://localhost:8000/scripts/match.php. The output is:
B
In the following script, the subject expression is not matched by any of the non-default conditional expressions.
<?php
echo match (3) {
0 => 'A',
1 => 'B',
2 => 'C',
default => 'Default',
};
The output is:
Default
The default
condition can't be grouped with other conditions as shown in the script:
<?php
echo match (3) {
0 => 'A',
1 => 'B',
2, default => 'C',
};
When the script is run, it generates the error:
Parse error: syntax error, unexpected token "default", expecting "=>"
A relatively complex example, the following script has a variable initialized from the \Ds\Vector
class. The subject expression accesses the vector variable in array notation.
<?php
$vector = new \Ds\Vector();
$vector->push('a');
$vector->push('b', 'c');
$vector[] = 'd';
echo match ($vector[1]) {
'a' => "a",
'b' => "b",
};
Call the script in a browser with url http://localhost:8000/scripts/match.php
The result is:
b
Assigning the return value to a variable
The return value may also be assigned to a variable, and the match conditions may be any arbitrary expressions. In the following script, the match expression matches the boolean value true as the subject expression with conditional expressions that call other built-in string functions.
<?php
$text = 'A B C D';
$result = match (true) {
str_word_count($text)==3 || str_word_count($text)==2 => '2 or 3',
str_word_count($text)==4 || str_word_count($text)==5 => '4 or 5',
};
var_dump($result);
The output is:
string(6) "4 or 5"
No type coercion
Unlike the loose comparison made by a switch
statement, the match
expression makes a strict comparison and no type coercion is made. First, an example of a loose comparison with a switch
statement. The following script matches the first condition and outputs the value of a
.
<?php
$var = 0;
switch((string)$var)
{
case 0 : echo 'a'; break; // This tests for NULL or empty string
default : echo 'b'; break; // Everything else, including zero
}
In contrast, the match
in the following script makes a strict comparison and matches the default condition to output Default
.
<?php
$var = 0;
echo match ((string)$var) {
0 => 'a',
default => 'Default',
};
Even NULL
values are matched using identity comparison
The match
expression makes an identity comparison using the identity operator (===
), which implies that even NULL
values are matched. As the Ds\Vector::push()
method returns NULL
, the match expression in the following script actually matches the NULL
values returned by the Ds\Vector::push()
method.
<?php
$vector = new \Ds\Vector();
$vector->push('a');
match ($vector->push('b')) {
$vector->push('d') => $vector->push('b'),
$vector->pop() => $vector->pop(),
};
print_r($vector);
The first condition gets matched and the output is:
Ds\Vector Object ( [0] => a [1] => b [2] => d [3] => b )
Identity comparison ( === )
can be applied to arbitrary values even to when no value is returned. The NULL
value returned by match can be assigned to a variable. The following script matches a NULL
value, and the first condition gets matched.
<?php
$vector = new \Ds\Vector();
$vector->push('a');
$vector = match ($vector->push('b')) {
$vector->push('c') => $vector->push('b'),
$vector->pop() => $vector->pop(),
};
var_dump($vector);
Call the script with url http://localhost:8000/scripts/sample.php and the output is:
NULL
Multiple conditions can be comma-separated
Multiple conditional expressions may be comma separated. First, an example with single conditions as in the following script.
<?php
$vector = new \Ds\Vector();
$vector->push('a');
match ('push') {
'push' => $vector->push('b'),
'pop' => $vector->pop(),
};
print_r($vector);
The output is :
Ds\Vector Object ( [0] => a [1] => b )
Take the same example and add multiple conditions separated by a comma.
<?php
$vector = new \Ds\Vector();
$vector->push('a');
match ('puush') {
'push','puush' => $vector->push('b')
};
print_r($vector);
The misspelled ‘puush’ that is listed as the second alternative in the first conditional expression list gets matched and the output is:
Ds\Vector Object ( [0] => a [1] => b )
Exhaustiveness
The match
expression must be exhaustive. In the following script the subject expression is not matched by any of the conditions.
<?php
$vector = new \Ds\Vector();
$vector->push('a');
match ('set') {
'push' => $vector->push('b'),
'pop' => $vector->pop(),
};
print_r($vector);
As a result, an error is output:
Uncaught UnhandledMatchError: Unhandled match case 'set'
The instanceof
operator supports arbitrary expression
Another new feature is that the instanceof operator accepts any arbitrary expression. The only two requirements are :
- The expression must be wrapped in parentheses, and
- The expression must evaluate to a string.
To demonstrate the new instanceof
operator, create a PHP script sample.php. Declare three variables for collections of different types such as \Ds\Vector
, and \Ds\Set
.
$collection_a = new \Ds\Vector([1, 2, 3]);
$collection_b = new \Ds\Vector();
$collection_c = new \Ds\Set();
Add an arbitrary function that returns a class, such as \Ds\Vector::class as a string.
function getSomeClass(): string
{
return \Ds\Vector::class;
}
Output the results for using the instanceof
operator with different collection variables. The instanceof
operator takes an expression that evaluates to a string in each call. The sample.php is listed.
<?php
$collection_a = new \Ds\Vector([1, 2, 3]);
$collection_b = new \Ds\Vector();
$collection_c = new \Ds\Set();
function getSomeClass(): string
{
return \Ds\Vector::class;
}
var_dump($collection_a instanceof ('\Ds'.'\Vector'));
var_dump($collection_a instanceof ('\Ds'.'\Hashtable'));
var_dump($collection_b instanceof ('\Ds'.'\Vector'));
var_dump($collection_b instanceof ('\Ds'.'\Set'));
var_dump($collection_c instanceof ('\Ds'.'\Set'));
var_dump(new \Ds\Vector instanceof (getSomeClass()));
var_dump($collection_a instanceof ('\Ds'.'\Set'));
The script runs ok with PHP 8 and generates output:
bool(true)
bool(false)
bool(true)
bool(false)
bool(true)
bool(true)
bool(false)
JIT(Just-In-Time) compiler
PHP 8 introduces Just-in-time (JIT) compilation with the following goals:
- Improve performance and usability.
- Make PHP suitable for non-Web CPU-intensive use cases, where substantial performance benefits can be expected.
- Create the potential for using PHP, instead of C, to develop built-in functions. PHP could be a better alternative as it does not have the same memory management and overflow issues as C does.
Two JIT compilation engines are introduced: Tracing JIT, and Function JIT. Function JIT only optimizes code within the scope of a single function, whereas Tracing JIT optimizes the whole stack trace. Tracing JIT is shown to improve performance 3 times on synthetic benchmarks, and 1.5 to 2 times on long-running queries. Tracing JIT is shown to improve performance by more than 4 times on the Mandelbrot benchmark.
In this article we have introduced some of the most relevant improvements to PHP 8 syntax, including attributes, the operator new, match expressions, and more. In the next article we shall explore improvements to classes and constructors.
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. |