What's New in PHP 8(Features, Changes, Security & JIT compiler)
PHP 8.0 is a major update of the PHP language. It contains many new features and optimizations including named arguments, union types, attributes, constructor property promotion, match expression, null safe operator, Saner string to number comparisons, Saner Numeric Strings, JIT Compiler, and improvements in the type system, error handling, and consistency. PHP 8 provides more security by utilizing improvements in the type system and better error handling. In this article, we are going to discuss what's New in PHP 8 in detail.
PHP 8.0 New Features
Named arguments
Named arguments allow passing arguments to a function based on the parameter name, rather than the parameter position. This means, that arguments are order-independent, and allow skipping default values arbitrarily. The below example helps you to get a better idea.
// Using positional arguments:
array_fill(0, 100, 50);
// Using named arguments:
array_fill(start_index: 0, num: 100, value: 50);
In the above example, arguments are passed in the same order as they are declared in the function signature. But other orders are also possible as given below.
array_fill(value: 50, num: 100, start_index: 0);
Another great advantage of named arguments is that they allow specifying only those arguments we want to change. We don’t have to specify default arguments if we don’t want to overwrite default values. The following example makes it clear:
htmlspecialchars($string, default, default, false);
// vs
htmlspecialchars($string, double_encode: false);
It is possible to use reserved keywords as the parameter name:
array_foobar(array: $value);
The parameter name must be an identifier, it's not possible to specify it dynamically:
// NOT supported.
function_name($variableStoringParamName: $value);
For deep knowledge see PHP RFC: Named Arguments
Union types
A “union type” accepts values of multiple different types, rather than a single one. PHP already supports two special union types:
- Type or null, using the special ?Type syntax.
- array or Traversable, using the special iterable type.
Union types are specified using the syntax Type1|Type2|... and can be used in all positions where types are currently accepted:
class Number {
private int|float $number;
public function setNumber(int|float $number): void {
$this->number = $number;
}
public function getNumber(): int|float {
return $this->number;
}
}
Union Types support all types supported by PHP with some limitations given below.
- The void type can never be part of a union. As such, types like T|void are illegal in all positions, including return types.
- The null type is supported as part of unions, such that T1|T2|null can be used to create a nullable union. The null type is only allowed as part of a union, and can not be used as a standalone type.
- Some functions like strpos(), strstr(), substr(), etc. include false among the possible return types, the false pseudo-type is also supported in union types. But the false pseudo-type cannot be used as a standalone type.
For more details see PHP RFC: Union Types
Attributes
Attributes are structured, syntactic metadata for the declaration of classes, properties, functions, methods, parameters, and constants.
Similar concepts exist in other languages named Annotations in Java, Attributes in C#, C++, Rust, Hack, and Decorators in Python, and Javascript.
Attributes are specially formatted text enclosed with "<<" and ">>". attributes may be applied to functions, classes, class constants, class properties, class methods, and function/method parameters. See the following examples.
<<ExampleAttribute>>
class Foo
{
<<ExampleAttribute>>
public const FOO = 'foo';
<<ExampleAttribute>>
public $x;
<<ExampleAttribute>>
public function foo(<<ExampleAttribute>> $bar) { }
}
$object = new <<ExampleAttribute>> class () { };
<<ExampleAttribute>>
function f1() { }
$f2 = <<ExampleAttribute>> function () { };
$f3 = <<ExampleAttribute>> fn () => 1;
Each declaration may have one or more attributes, and each attribute may have one or more associated values:
<<WithoutArgument>>
<<SingleArgument(0)>>
<<FewArguments('Hello', 'World')>>
function foo() {}
See PHP RFC: Attributes for more details.
Constructor Property Promotion
Constructor Property Promotion makes the property declaration simple, shorter, and less redundant.
Let’s take the below example,
class Point {
public float $x;
public float $y;
public float $z;
public function __construct(
float $x = 0.0,
float $y = 0.0,
float $z = 0.0,
) {
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}
The properties are repeated 1) in the property declaration, 2) in the constructor parameters, and 3) two times in the property assignment. Additionally, the property type is repeated twice. The above code results in a lot of boilerplate and makes changes more complicated and error prone.
PHP8 introduces a shorthand syntax, which allows combining the definition of properties and the constructor:
class Point {
public function __construct(
public float $x = 0.0,
public float $y = 0.0,
public float $z = 0.0,
) {}
}
Constructor promotion can be used in conjunction with inheritance but has no special interaction with it beyond what is implied by the desugaring.
class Vehicle {
public function __construct(
public int $x = 0
) {}
}
class Car extends Vehicle{
public function __construct(
$x,
public int $y = 0,
public int $z = 0,
) {
parent::__construct($x);
}
}
Not allowed in constructor property promotion:
1. Promoted parameters may only occur inside non-abstract constructors. As such, all of the following is illegal:
// Error: Not a constructor.
function test(private $x) {}
abstract class Test {
// Error: Abstract constructor.
abstract public function __construct(private $x);
}
interface Test {
// Error: Abstract constructor.
public function __construct(private $x);
}
2. Promoted properties have to be prefixed by one of the visibility keywords, use of var is not supported:
class Test {
// Error: "var" keyword is not supported.
public function __construct(var $prop) {}
}
3. Properties declared through promoted parameters are subject to the same restrictions as normal property declarations. In particular, it is not possible to declare the same property twice:
class Test {
public $prop;
// Error: Redeclaration of property.
public function __construct(public $prop) {}
}
It is also not possible to use the callable type, because it is not supported as a property type. Similarly, because promoted parameters imply a property declaration, nullability must be explicitly declared and is not inferred from a null default value. Variadic parameters cannot be promoted.
For deep knowledge see PHP RFC: Constructor Property Promotion
Match expression
The new match expression is similar to the switch but with safer semantics and the ability to return values.
switch ($x) {
case 0:
$result = 'Car';
break;
case 1:
$result = 'Bus';
break;
case 2:
$result = 'Bike';
break;
}
echo $result;
We can rewrite the above code with a match expression.
echo match ($x) {
0 => 'Car',
1 => 'Bus',
2 => 'Bike',
};
Multiple conditions can be comma-separated to execute the same block of code.
echo match ($x) {
1, 2 => 'Same for 1 and 2',
3, 4 => 'Same for 3 and 4',
};
The switch statement loosely compares (==) the given value to the case values. But the match expression uses strict comparison (===) instead.
Learn more about match expression at PHP RFC: Match expression
nullsafe operator
PHP 8 Introduces the new nullsafe operator ?-> with full short-circuiting.
In previous versions of PHP checking for null leads to deeper nesting and repetition:
$country = null;
if ($session !== null) {
$user = $session->user;
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$country = $address->country;
}
}
}
// do something with $country
With the nullsafe operator ?-> this code could instead be written as:
$country = $session?->user?->getAddress()?->country;
// do something with $country
In short-circuiting, when the evaluation of one element in the chain fails the execution of the entire chain is aborted and the entire chain evaluates to null.
For more examples See PHP RFC: Nullsafe operator
Saner string to number comparisons
Comparisons between strings and numbers using == and other non-strict comparison operators currently work by casting the string to a number and subsequently performing a comparison on integers or floats. This results in many surprising comparison results, the most notable of which is that 0 == "foobar" returns true.
$validValues = ["foo", "bar", "baz"];
$value = 0;
var_dump(in_array($value, $validValues));
// bool(true)
Unfortunately, while the idea of non-strict comparisons has some merit, their current semantics are blatantly wrong in some cases and thus greatly limit the overall usefulness of non-strict comparisons.
Saner string to number comparisons intends to give the string to number comparisons a more reasonable behavior: When comparing to a numeric string, use a number comparison (same as now). Otherwise, convert the number to a string and use a string comparison. The following table shows how the result of some simple comparisons.
Comparison | Before | After |
---|---|---|
0 == "0" | true | true |
0 == "0.0" | true | true |
0 == "foo" | true | false |
0 == "" | true | false |
42 == " 42" | true | true |
42 == "42foo" | true | false |
For more deep knowledge, see PHP RFC: Saner string to number comparisons.
Saner Numeric Strings
The concept of numeric strings means strings that can be interpreted as numbers. A string with numbers can be categorized in three ways:
- A numeric string is a string containing only a number, optionally preceded by whitespace characters. For example, "123" or " 1.23e2".
- A leading-numeric string is a string that begins with a numeric string and is followed by non-number characters or whitespace characters. For example, "123abc" or "123 ".
- A non-numeric string is a string which is neither a numeric string nor a leading-numeric string.
This RFC proposes to unify the various numeric string modes into a single concept: Numeric characters only with both leading and trailing whitespace allowed. Any other type of string which is non-numeric will throw TypeErrors when used in a numeric context.
This means all strings which currently emit the E_NOTICE “A non-well formed numeric value encountered” will be reclassified into the E_WARNING “A non-numeric value encountered” except if the leading-numeric string contained only trailing whitespace. And the various cases which currently emit an E_WARNING will be promoted to TypeErrors.
For more about Saner Numeric Strings, see PHP RFC: Saner numeric strings
PHP 8.0 New Functions
str_contains
str_contains checks if a string is contained in another string and returns a boolean value true if the string was found and returns false if the string is not found.
In previous versions of PHP, strpos and strstr are used for this has a few downsides:
- not very intuitive for a reader
- easy to get wrong (especially with the !== comparison)
- or hard to remember for new PHP developers.
The below code example makes clear the syntax and how str_contains function works:
<?php
str_contains("abc", "a"); // true
str_contains("abc", "d"); // false
// $needle is an empty string
str_contains("abc", ""); // true
str_contains("", ""); // true
?>
str_starts_with() and str_ends_with()
str_starts_with and str_ends_with are new PHP functions similar to str_contains. But, str_starts_with checks if a string begins with another string and returns a boolean value (true/false) whether it does. And str_ends_with checks if a string ends with another string and returns a boolean value (true/false) whether it does.
Syntax:
str_starts_with (string $haystack , string $needle) : bool
str_ends_with (string $haystack , string $needle) : bool
get_debug_type
New function get_debug_type will return the given type of a variable. This function would differ from gettype in that it would return native type names, e.g. “int” rather than “integer” and would automatically resolve class names. The following table shows what get_debug_type() returns for different values, and what gettype() returns for the same value (if it is different):
Value | get_debug_type() | gettype() |
0 | int | integer |
0.1 | float | double |
true | bool | boolean |
false | bool | boolean |
“hello” | string | |
[] | array | |
null | null | NULL |
A class with name “Foo\Bar” | Foo\Bar | object |
An anonymous class | class@anonymous | object |
A resource | resource (xxx) | resource |
A closed resource | resource (closed) |
The below example shows how gettype() works
$bar = $arr['key'];
if (!($bar instanceof Foo)) {
// this shows the most simple of patterns, to get the real type an assoc array
// must be present to convert long-form "integer" into int etc.
throw new TypeError('Expected ' . Foo::class . ' got ' . (is_object($bar) ? get_class($bar) : gettype($bar)));
}
In PHP 8.0 we can use get_debug_type function for the above use case as follows:
if (!($bar instanceof Foo)) {
throw new TypeError('Expected ' . Foo::class . ' got ' . get_debug_type($bar));
}
PHP JIT Compiler (Just in Time Compiler)
PHP 8.0 comes with the new feature JIT (Just-In-Time) Compilation. Interpreted programming languages compile at the run time and directly execute the code in a virtual machine. Programming languages with Ahead-Of-Time (AOT) compilation, on other hand, requires the code to be compiled first before it runs.
JIT compilation is a hybrid model of interpreter and Ahead-of-Time compilation, that some or all of the code is compiled, often at run-time, without requiring the developer to manually compile it.
PHP JIT is implemented as an almost independent part of OPcache. It may be enabled/disabled at PHP compile time and at run-time. When enabled, native code of PHP files is stored in an additional region of the OPcache shared memory and op_array->opcodes[].handler(s) keep pointers to the entry points of JIT-ed code. This approach doesn't require engine modification at all.
For better understanding, first, we can look into 4 stages of PHP execution:
- Lexing/Tokenizing: First, the interpreter reads the PHP code and builds a set of tokens.
- Parsing: The interpreter checks if the script matches the syntax rules and uses tokens to build an Abstract Syntax Tree (AST), a hierarchical representation of the structure of the source code.
- Compilation: The interpreter traverses the tree and translates AST nodes into low-level Zend opcodes, which are numeric identifiers determining the type of instruction performed by the Zend VM.
- Interpretation: Opcodes are interpreted and run on the Zend VM.
Even if opcodes are low-level intermediate representations, they still have to be compiled into machine code. JIT “doesn’t introduce any additional IR (Intermediate Representation) form,” but uses DynASM (Dynamic Assembler for code generation engines) to generate native code directly from PHP byte-code.
In short, JIT translates the hot parts of the intermediate code into machine code. Bypassing compilation, it’d be able to bring considerable improvements in performance and memory usage.
Configuring JIT
In PHP 8.0, JIT is enabled by default but turned off. We can configure JIT in the php.ini file below configurations.
opcache.enable=1
opcache.enable_cli=1
opcache.jit_buffer_size=256M
JIT is implemented as part of Opcache, and requires the Opcache extension to be enabled. So we made
opcache.enable=1
opcache.enable_cli=1
Opcache.jit_buffer_size accepts how much memory JIT is allowed to use for its buffer. To disable JIT set jit_buffer_size to 0.
To enable and set a buffer size, set a positive value in bytes, or with standard PHP data size suffixes (M, G, etc.) like:
opcache.jit_buffer_size=256M
Debugging JIT (opcache.jit_debug)
PHP JIT provides a way to emit JIT debug information by enabling jit_debug into 1 in the php.ini file. When debug was enabled, it outputs the assembly code for further inspection.
opcache.jit_debug=1
Summary
We have covered new features, optimization, new functions, Just In Time compiler etc. Hope this blog helped you to get a good understanding of PHP 8.
On datainfinities.com, Read articles all around JavaScript, React, Node.js, PHP, Laravel, and Shopify.
Related Blogs
Laravel Cron Job Task Scheduling Tutorial
How to solve error: array_merge() does not accept unknown named parameters
How to create multiple where clause query using Laravel eloquent
419 page expired error in Laravel
Get raw SQL query from Laravel Query Builder
Laravel Order by Pivot Table Field
Delete an element from an array in PHP
How to display validation error messages in Laravel blade view
Explore All Blogs