Lecture
PHP 8, the new major version of PHP, is expected to be released on December 3, 2020. It is currently under very active development, so a lot is likely to change in the coming months.
In this post I will keep an up-to-date list of what's expected: new features, performance improvements, and breaking changes. Since PHP 8 is a new major version, there is a good chance your code could break. However, if you have been keeping up with the latest releases, upgrading shouldn't be too difficult, since most of the breaking changes were already deprecated in earlier 7.* versions.
Besides breaking changes, PHP 8 also has a number of nice new features, such as the JIT compiler and union types; and that's not all!
PHP 8 is still under active development, so this list will grow and change over time.

Given PHP's dynamically typed nature, there are many cases where union types can be useful. Union types are a set of two or more types that indicate that any of them may be used.
public function foo(Foo|Bar $input): int|float;
Note that void can never be part of a union type, since it indicates "no return value at all". In addition, nullable unions can be written using |null or using the existing ? notation:
public function foo(Foo|null $foo): void; public function bar(?Bar $bar): void;
The JIT compiler promises significant performance improvements, though not always in the context of web requests.
No exact benchmarks have been made yet, but they will certainly come.
How does it work?
There is a so-called "monitor" that will watch the code while it runs. When this monitor detects repeated parts of your code, it flags these parts as "warm" or "hot", depending on frequency.
These hot parts can be compiled into optimized machine code and used on the fly instead of the actual code.
The JIT compiler can significantly improve your program's performance, but doing this correctly is not easy.
However, since PHP is most often used in a web context, we should also measure the impact of JIT there.
It turns out that, unfortunately, there is much less code involved when handling a web request. This doesn't mean JIT can't improve web performance at all, but we won't see gains similar to the fractal example.
Is this a reason to abandon JIT? Definitely not! There are good arguments for adding it, even though it might not have the performance impact we're hoping for.
These are solid arguments in favor of JIT.
However, having machine code as output will make it harder to debug possible bugs in PHP's JIT compiler.
Attributes, commonly known as annotations in other languages, offer a way to add metadata to classes without parsing docblocks.
Here is an example of what attributes look like, from the RFC:
use App\Attributes\ExampleAttribute;
<>
class Foo
{
<>
public const FOO = 'foo';
<>
public $x;
<>
public function foo(<> $bar) { }
}
<>
class ExampleAttribute
{
public $value;
public function __construct($value)
{
$this->value = $value;
}
}
Although returning self was already possible, static was not a valid return type until PHP 8.
Given PHP's dynamically typed nature, this feature will be useful for many developers.
class Foo
{
public function test(): static
{
return new static();
}
}
This RFC turns throw from a statement into an expression, which allows exceptions to be thrown in many new places:
$triggerError = fn () => throw new MyError();
$foo = $bar['offset'] ?? throw new OffsetDoesNotExist('offset');
Built on top of the weak references RFC added in PHP 7.4, a WeakMap implementation is added in PHP 8, in which WeakMaps store references to objects that don't prevent those objects from being garbage collected.
Take the example of an ORM: they often implement caches that hold references to entity classes to improve the performance of relationships between entities. These entity objects cannot be collected as long as the cache holds a reference to them, even if the cache is the only reference to them.
If this caching layer uses weak references and maps instead, PHP will garbage collect these objects when nothing else references them anymore. Especially in the case of ORMs, which can manage several hundred if not thousands of objects per request, weak maps can offer a better, more resource-friendly way to work with these objects.
Here is what weak maps look like, an example from the RFC:
class Foo
{
private WeakMap $cache;
public function getSomethingWithCaching(object $obj): object
{
return $this->cache[$obj]
??= $this->computeSomethingExpensive($obj);
}
}
A small but useful new feature: it's now possible to use ::class on objects,
instead of using get_class() on them. This works the same way as get_class().
$foo = new Foo(); var_dump($foo::class);
While this was already possible when calling a function, parameter lists still lacked support for a trailing comma. This is now allowed in PHP 8, which means you can do the following:
public function(
string $parameterA,
int $parameterB,
Foo $objectfoo,
) {
// …
}
You could already create a DateTime object from a DateTimeImmutable object using DateTime::createFromImmutable($immutableDateTime), but the reverse was harder. By adding DateTime::createFromInterface() and DatetimeImmutable::createFromInterface() there is now a generalized way to convert DateTime and DateTimeImmutable objects into each other.
DateTime::createFromInterface(DateTimeInterface $other); DateTimeImmutable::createFromInterface(DateTimeInterface $other);
The Stringable interface can be used to type-hint anything that is a string or implements __toString(). In addition, whenever a class implements __toString(), it automatically implements the interface behind the scenes, and there is no need to implement it manually.
class Foo
{
public function __toString(): string
{
return 'foo';
}
}
function bar(Stringable $stringable) { /* … */ }
bar(new Foo());
bar('abc');
Some might say it's long overdue, but we finally no longer need to rely on strpos to find out whether a string contains another string.
Instead of this:
if (strpos('string with lots of words', 'words') !== false) { /* … */ }
You can now do this
if (str_contains('string with lots of words', 'words')) { /* … */ }
Two other long-overdue additions, these two functions are now added to the core.
str_starts_with('haystack', 'hay'); // true
str_ends_with('haystack', 'stack'); // true
The new fdiv() function does something similar to the fmod() and intdiv() functions, allowing division by 0. Instead of an error, you get INF, -INF, or NAN, depending on the case.
get_debug_type() returns the type of a variable. It seems like gettype() would fit the bill?
But get_debug_type() returns more useful output for arrays, strings, anonymous classes, and objects.
For example, calling gettype() on a class \Foo\Bar will return object. Using get_debug_type() will return the class name instead.
A full list of differences between get_debug_type() and gettype() can be found in the RFC.
Traits will be able to contain abstract methods that must be implemented by the classes using them.
However, there is a caveat: before PHP 8, the signature of these method implementations was not checked.
The following used to be valid:
trait Test {
abstract public function test(int $input): int;
}
class UsesTrait
{
use Test;
public function test($input)
{
return $input;
}
}
PHP 8 will perform proper method signature checking when using a trait and implementing its abstract methods.
This means you now need to write this instead:
class UsesTrait
{
use Test;
public function test(int $input): int
{
return $input;
}
}
The token_get_all() function returns an array of values. This RFC adds a PhpToken class with a PhpToken::getAll() method. This implementation works with objects instead of plain values. It consumes less memory and is easier to read.
From the RFC: "The uniform variable syntax RFC resolved a number of inconsistencies in PHP's variable syntax.
This RFC intends to address a small portion of the missed cases".
https://wiki.php.net/rfc/variable_syntax_tweaks
Many people have asked for proper type annotations to be added for all internal functions.
This has been a long-standing issue, and it can finally be resolved thanks to all the changes made to PHP in previous versions
. This means internal functions and methods will have full type information in reflection.
As mentioned earlier: this is a major upgrade, and therefore there will be breaking changes. The best thing to do is look at the full list of breaking changes in the UPGRADING document.
https://github.com/php/php-src/blob/master/UPGRADING#L20
Many of these breaking changes were already deprecated in previous 7.* versions, so if you've kept up over the last few years, moving to PHP 8 shouldn't be too hard.
User-defined functions in PHP already generate TypeErrors, but internal functions did not — they instead issued warnings and returned null. Starting with PHP 8, the behavior of internal functions has become consistent.
A number of errors that previously only triggered warnings or notices have been converted into proper errors. The following warnings have been changed.
It's quite possible this change may reveal errors that were previously hidden before PHP 8.
Be sure to set display_errors=Off on your production servers!
It is now E_ALL instead of everything except E_NOTICE and E_DEPRECATED. This means many errors that were previously silently ignored may now appear, even though they may have already existed before PHP 8.
From the RFC: the current default error mode for PDO is silent. This means that when an SQL error occurs, no errors or warnings are raised and no exceptions are thrown unless the developer implements their own explicit error handling.
This RFC changes the default error mode, which will change to PDO::ERRMODE_EXCEPTION in PHP 8.
Although this change was already deprecated in PHP 7.4, it now takes effect. If you had written something like this:
echo "sum: " . $a + $b;
PHP would previously interpret it like this:
echo ("sum: " . $a) + $b;
PHP 8 will make it interpreted like this:
echo "sum: " . ($a + $b);
Before PHP 8 it was possible to apply arithmetic or bitwise operators to arrays, resources, or objects. This is no longer possible and will throw a TypeError:
[] % [42]; $object + 4;
Three class method signatures in reflection have been changed:
ReflectionClass::newInstance($args); ReflectionFunction::invoke($args); ReflectionMethod::invoke($object, $args);
They are now:
ReflectionClass::newInstance(...$args); ReflectionFunction::invoke(...$args); ReflectionMethod::invoke($object, ...$args);
The upgrading guide states that if you extend these classes and still want to support both PHP 7 and PHP 8, the following signatures are allowed:
ReflectionClass::newInstance($arg = null, ...$args); ReflectionFunction::invoke($arg = null, ...$args); ReflectionMethod::invoke($object, $arg = null, ...$args);
During the development of PHP 7.*, several deprecations were introduced that are now finalized in PHP 8.
Comments