
New features in PHP 8
Conversion Work
One of the major innovations in PHP is intended to give the scripting language a boost, but it does require some prior knowledge. As of version 5.5, the code to be executed has been stored in a cache provided by the OPcache extension [1]. Thanks to this cache, the interpreter does not have to reread the scripts for every request.
PHP 8 sees OPcache expanded to include a just-in-time (JIT) compiler. This converts parts of the PHP code into native program code, which the processor then executes directly and far faster as a consequence. As a further speed tweak, the generated binary code is also cached. When the PHP code is restarted, PHP 8 simply accesses the precompiled binary code from the cache.
As the first benchmarks by PHP developer Brent Roose show [2], scripts with repetitive or extensive calculations obviously benefit from these changes. When handling individual short requests, the JIT compiler shows its advantages to a far lesser extent.
Abbreviation
The syntax, which has changed slightly in some places in PHP 8, saves developers some typing. For example, the scripting language adds a more powerful and more compact match
function to supplement switch
. In Listing 1, $flasher
is only On
if $lever
is set to Up
or Down
. If none of the comparison values before =>
are correct, the default
value applies.
Listing 1: match
$flasher = match ($lever) { 0 => "Warning_Flasher", 'Up', 'Down' => "On", default => "Off" };
The call to $user->address->getBirthday()->asString()
in Listing 2 only works if $address
exists and getBirthday()
returns a valid object. To do this, developers previously had to nest several if
branches. The new nullsafe operator ?->
checks the existence automatically and reduces the test to one line (line 15).
Listing 2: Happy Birthday?
01 // ---- previously -------- 02 if ($user !== null) { 03 $a = $user->address; 04 05 if ($a !== null) { 06 $b = $user->getBirthday(); 07 08 if ($b !== null) { 09 $bday = $b->asString(); 10 } 11 } 12 } 13 14 // ---- in PHP 8 -------- 15 $bdate = $user?->address?->getBirthday()?->asString();
Where a class needs to encapsulate an address or other data, the developer usually first notes the appropriate variables, which are then assigned values by a constructor (Listing 3, lines 2-12). In PHP 8, this can be written in a concise way (Listing 3, lines 15-21).
Listing 3: Encapsulating Data
01 // ---- previously -------- 02 class Address 03 { 04 public string $name; 05 public DateTimeImmutable $birthday; 06 07 public function __construct(string $n, DateTimeImmutable $g) 08 { 09 $this->name = $n; 10 $this->$birthday = $g; 11 } 12 } 13 14 // ---- in PHP 8 -------- 15 class Address 16 { 17 public function __construct( 18 public string $name, 19 public DateTimeImmutable $birthday, 20 ) {} 21 }
The constructor now collects all the required information from which PHP 8 automatically derives the complete class structure. Thanks to the Constructor Property Promotion syntax, programmers can create data objects far faster and also refactor them more quickly later.
Exceptional Phenomenon
Until now, PHP developers always had to catch exceptions in a variable, even if they didn't want to do anything else with the corresponding object. From PHP 8 on, in such situations, you simply omit the variable and just specify the type. If you want to handle all errors in the catch
block, simply catch Throwable
(Listing 4).
Listing 4: Throwable
try { [...] } catch (Throwable) { Log::error("Error!"); }
The get_class()
function returns an object's class name. Developers using PHP 8 or higher can access this class with an appended ::class
, as shown in Listing 5.
Listing 5: Class Name
$a = new Address(); var_dump($a::class);
Typical!
Functions expect their parameters in a given order. Especially with a large number of parameters, it is easy to lose track. The following code, which calculates the volume of a box, for example, does not make it entirely clear which value stands for the width:
$v = volume(10, 3, 2);
In PHP 8, developers can explicitly note the names of the corresponding parameters to ensure clarity. When using these named arguments, you also choose the order of the parameters according to your needs. Optional parameters can also be omitted, like $depth
in the example shown in Listing 6. By the way, Listing 6 shows a further innovation: After the last parameter, you can use another comma, even if no further parameters follow.
Listing 6: Named Parameters
function volume(int $width, int $height, int $depth = 1) { return $width * $height * $depth; } $v = volume(height: 3, width: 10,);
Flextime
When you compute with integers, floating-point numbers are often generated. Wouldn't it be useful if the corresponding function could return an int
or a float
value as needed. In PHP 8, this is achieved with union types. You simply combine several possible data types with a pipe character (|
). In the example shown in Listing 7, area()
returns either an int
or a float
value.
Listing 7: Data Types with Pipe
class Rect { public int|float $x, $y, $w, $h; public function area(): int|float { return $this->w * $this->h; } }
Another new data type is mixed
. It stands for one of the data types array
, bool
, callable
, int
, float
, null
, object
, resource
, or string
. The mixed
data type always occurs if the type information is missing. For example, you can use mixed
to indicate that you cannot or do not want to specify the type of a variable at the corresponding position.
The new WeakMap
data structure works much like an array but uses objects as keys (Listing 8). Garbage Collection can also collect the used objects, so if the program destroys $a
at some point further downstream (e.g., intentionally with unset($a);
), PHP would automatically remove the corresponding $cache[$a]
entry.
Listing 8: WeakMap
$cache = new WeakMap; $a = new Address; $cache[$a] = 123;
In Figure 1, var_dump()
outputs WeakMap
once; an address
object acts as the key
here. Then unset()
destroys this object, which automatically removes the entry from WeakMap
. This is proven by the second output of var_dump()
(shown in the last two lines of Figure 1). One of WeakMap
's main areas of application is customized caching.

WeakMap
disappears along with the object.More Functional
Whether or not the Hello World
string contains the word World
is determined by the strpos()
function. In PHP 8, there is an alternative to this: str_contains()
(Listing 9). The siblings str_starts_with()
and str_ends_with()
, which search for the word at the beginning and at the end of the string, respectively, are new. The fdiv()
function divides a floating-point number by zero without grumbling and returns INF
, -INF
, or NAN
.
Listing 9: str_contains()
if (str_contains('Hello World', 'World')) { [...] }
The get_debug_type()
function determines the data type of a variable. In contrast to the already existing gettype()
, the new function also identifies strings, arrays, and closed resources, and it reveals the classes of objects. As the function name suggests, it is mainly intended to make writing debug messages easier. Each resource, such as an open database connection, is assigned an internal identification number. Developers now can also address them in a type-agnostic way using get_resource_id()
.
The well-known token_get_all()
function returns the matching PHP tokens for PHP source code [3]. You can have either a string or an array, which is anything but handy. This has led to PHP 8 introducing the PhpToken
class. Its getAll()
method returns an array of PhpToken
objects, which in turn encapsulate the individual tokens. This token_get_all()
replacement is easier to use, but at the price of using more memory.
Attributes
Many other languages offer annotations to let programmers attach metadata to classes. In this way, the developers can make a note of, say, the database table in which the class stores its data. PHP 8 now has an option for this in the form of attributes. The actual information is located between #[ ... ]
directly in front of the class, as shown in Listing 10.
Listing 10: Attributes
#[DatabaseTable("User")] class User { #[DatabaseColumn] public $name; public function setBirthday(#[ExampleAttribute] $bday) { } }
As the example demonstrates, attributes can be attached to classes, but also to variables, constants, methods, functions, and parameters. The information's structure and content is determined by the developer or a framework that evaluates the attributes. In Listing 10, the class is assigned an attribute of DatabaseTable
. If necessary, you can pass in parameters in the brackets. The example reveals that the database table is named User
.
The PHP developers have been working on the syntax for attributes for quite some time. There was some talk of using <<ExampleAttribute>>
and @@ExampleAttributes
as tags, and you will find references to this in numerous posts on PHP 8.
Attributes can be read using the Reflection API. The example shown in Listing 11 uses the new getAttributes()
method to get all the attributes for the User
class in the form of an array with all attributes. Each one encapsulates an object of the type ReflectionAttributes
. Among other things, this new class has a getName()
method that reveals the name of the attribute.
Listing 11: Reading Attributes
01 $reflectionClass = new \ReflectionClass(User::class); 02 $attributes = $reflectionClass->getAttributes(); 03 var_dump($attributes[0]->getName()); 04 var_dump($attributes[0]->getArguments());
The getName()
method is used in line 3 of Listing 11, which simply outputs the name of the attribute via var_dump()
– in the DatabaseTable
example. Similarly, getArguments()
in line 4 returns the corresponding parameters as an array (Figure 2).
![For the #[DatabaseTable("User")] attribute, the Reflection API correctly returns DatabaseTable as the name and User as the parameter. All parameters are bundled into an array. For the #[DatabaseTable("User")] attribute, the Reflection API correctly returns DatabaseTable as the name and User as the parameter. All parameters are bundled into an array.](images/b02_attributes-hell.png)
#[DatabaseTable("User")]
attribute, the Reflection API correctly returns DatabaseTable
as the name and User
as the parameter. All parameters are bundled into an array.Small Matters
Sorting functions in PHP have not been stable thus far, leaving the order of identical elements in the sorted results to chance. In PHP 8, all sorting routines adopt identical elements in the order that existed in the original array.
The PHP developers have also introduced a new Stringable
interface that automatically implements a class if it offers the __toString()
function. The string|Stringable
union type then accepts both strings and objects with the __toString()
method. This in turn is intended to improve type safety.
Since the JSON data format is now the format of choice in many web applications, PHP 8 can no longer be compiled without the JSON extension. Developers can more easily convert DateTime
and DateTimeImmutable
into each other using DateTime::createFromInterface()
and DateTimeImmutable::createFromInterface()
. static
can also be used as the return type (Listing 12).
Listing 12: static
class Foo { public function create(): static { return new static(); } }
Traits
Developers can use traits to smuggle functions into classes without paying attention to the inheritance hierarchy [4]. Listing 13 shows an example of this: A trait can also define abstract functions, which the individual classes must implement in turn. PHP 8 is the first version to check function signatures. The implementation of the Circle
class thus throws an error due to the wrong string
(Figure 3).
Listing 13: Traits
trait Coordinates { abstract public function area(): int; } class Rectangle { use Coordinates; public function area(): int { [...] } } class Circle { use Coordinates; public function area(): string { [...] } }

Circle
class implements the area()
function incorrectly.Elsewhere PHP is more agnostic: Until now, the interpreter applied some inheritance rules to private methods, even if they were not visible in the derived class. In PHP 8 this no longer happens, which means that the code shown in Listing 14 now runs without an error message.
Listing 14: Inheritance Theory
class Foo { final private function calc() { [...] } } class Bar extends Foo { private function calc() { [...] } }
Changes
PHP 8 irons out some inconsistencies and breaks backwards compatibility. For example,
0 == "foo"
is now considered false. PHP 8 only evaluates the .
operator for concatenating strings after an addition or subtraction. Code such as
echo "Length: " . $y - $x;
is now interpreted by PHP 8 as
echo "Length: " . ($y - $x);
Namespaces may no longer contain spaces in their names, but from now on reserved keywords are also allowed as parts of a namespace identifier.
Within the Reflection API, the signatures of some methods have changed. For example, instead of ReflectionClass::newInstance($args);
, PHP 8 uses the ReflectionClass::newInstance(...$args);
method. However, if you want the PHP code to run under PHP 7 and 8, the PHP team recommends using the notation shown in Listing 15.
Listing 15: Reflection Class Notation
ReflectionClass::newInstance($arg = null, ...$args);
Stricter Reporting Requirements
If you call existing scripts in PHP 8, you can expect numerous error messages. Besides some incompatible changes, the main reason for these errors is stricter handling. From now on, the error report level E_ALL
will be used by default. Additionally, the @
operator can be used to suppress fatal errors. Developers must also be prepared for SQL errors when connecting to databases via the PDO interface: Error handling now uses exception mode by default (PDO::ERRMODE_EXCEPTION
).
Arithmetic and bitwise operators throw a TypeError
if one of the operands is an array, a resource, or not an overloaded object. One exception occurs if you merge two arrays using array + array
. Division by zero throws a DivisionByZeroError
in PHP 8.
Finally, PHP 8 removes some functions and language constructs already tagged as deprecated
in version 7.x. The PHP developers meticulously list all incompatible changes in a long document online [5]. If you need to maintain existing PHP code, you will definitely want to check out this document.
Conclusions
PHP 8 includes many useful new features that make the developer's life easier and provide for more compact code. In particular, the new match
, Constructor Property Promotion, and attributes should quickly find many friends. In addition, PHP 8 cautiously throws out some outdated behaviors and constructs. Existing PHP applications, however, will most likely need to be adapted as a result. Whether the JIT compiler really delivers the hoped-for performance boost or only gives PHP code a boost in special cases remains to be seen in practice.