What’s new in PHP 8 !
--
PHP 8 will be released on December 3, 2020. It’s a new major version, which means that it will introduce some breaking changes, as well as lots of new features and performance improvements. PHP 8 is in very active development right now, with its first alpha expected on June 18, 2020.
Because of the breaking changes, there’s a higher chance you’ll need to make some changes in your code to get it running on PHP 8. If you’ve kept up to date with the latest releases though, the upgrade shouldn’t be too hard, since most breaking changes were deprecated before in the 7.* versions. And don’t worry, all these deprecations are listed in this post.
Besides breaking changes, PHP 8 also brings a nice set of new features such as the JIT compiler, union types, attributes, and more.
#New features
Starting with new features, remember that PHP 8 is still in active development, so this list will grow over time.
#Union types rfc
Given the dynamically typed nature of PHP, there are lots of cases where union types can be useful. Union types are a collection of two or more types which indicate that either one of those can 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". Furthermore, nullable
unions can be written using |null
, or by using the existing ?
notation:
public function foo(Foo|null $foo): void;public function bar(?Bar $bar): void;
#JIT rfc
The JIT — just in time — compiler promises significant performance improvements, albeit not always within the context of web requests. There haven’t been any accurate benchmarks done at this point, but they sure will come.
If you want to know more about what the JIT can do for PHP, you can read another post I wrote about it here.
#Attributes rfc
Attributes, commonly known as annotations in other languages, offers a way to add meta data to classes, without having to parse docblocks.
As for a quick look, here’s an example of what attributes look like, from the RFC:
use App\Attributes\ExampleAttribute;<<ExampleAttribute>>
class Foo
{
<<ExampleAttribute>>
public const FOO = 'foo';
<<ExampleAttribute>>
public $x;
<<ExampleAttribute>>
public function foo(<<ExampleAttribute>> $bar) { }
}<<PhpAttribute>>
class ExampleAttribute
{
public $value;
public function __construct($value)
{
$this->value = $value;
}
}
If you want to take a deep dive in how attributes work, and how you can build your own; you can read about attributes in depth on this blog.
#Constructor property promotion rfc
This RFC adds syntactic sugar to create value objects or data transfer objects. Instead of specifying class properties and a constructor for them, PHP can now combine them into one.
Instead of doing this:
class Money
{
public Currency $currency;
public int $amount;
public function __construct(
Currency $currency,
int $amount,
) {
$this->currency = $currency;
$this->amount = $amount;
}
}
You can now do this:
class Money
{
public function __construct(
public Currency $currency,
public int $amount,
) {}
}
There are lots of variations of this syntax, head over to the RFC to read about them.
#New mixed
type rfc
Some might call it a necessary evil: the mixed
type causes many to have mixed feelings. There's a very good argument to make for it though: a missing type can mean lots of things in PHP:
- A function returns nothing or null
- We’re expecting one of several types
- We’re expecting a type that can’t be type hinted in PHP
Because of the reasons above, it’s a good thing the mixed
type is added. mixed
itself means one of these types:
array
bool
callable
int
float
null
object
resource
string
Note that mixed
can also be used as a parameter or property type, not just as a return type.
Also note that since mixed
already includes null
, it's not allowed to make it nullable. The following will trigger an error:
// Fatal error: Mixed types cannot be nullable, null is already part of the mixed type.
function bar(): ?mixed {}
#Weak maps rfc
Built upon the weakrefs RFC that was added in PHP 7.4, a WeakMap
implementation is added in PHP 8. WeakMaps
hold references to objects, which don't prevent those objects from being garbage collected.
Take the example of ORMs, they often implement caches which hold references to entity classes to improve the performance of relations between entities. These entity objects can not be garbage collected, as long as this cache has a reference to them, even if the cache is the only thing referencing 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 hundreds, if not thousands of entities within a request; weak maps can offer a better, more resource friendly way of dealing with these objects.
Here’s 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);
}
}
#Non-capturing catches rfc
Whenever you wanted to catch an exception before PHP 8, you had to store it in a variable, regardless whether you used that variable or not. With non-capturing catches, you can omit the variable, so instead of this:
try {
// Something goes wrong
} catch (MySpecialException $exception) {
Log::error("Something went wrong");
}
You can now do this:
try {
// Something goes wrong
} catch (MySpecialException) {
Log::error("Something went wrong");
}
Note that it’s required to always specify the type, you’re not allowed to have an empty catch
. If you want to catch all exceptions and errors, you can use Throwable
as the catching type.
#Create DateTime
objects from interface
You can already create a DateTime
object from a DateTimeImmutable
object using DateTime::createFromImmutable($immutableDateTime)
, but the other way around was tricky. By adding DateTime::createFromInterface()
and DatetimeImmutable::createFromInterface()
there's now a generalised way to convert DateTime
and DateTimeImmutable
objects to each other.
DateTime::createFromInterface(DateTimeInterface $other);DateTimeImmutable::createFromInterface(DateTimeInterface $other);
#New Stringable
interface rfc
The Stringable
interface can be used to type hint anything that is a string or implements __toString()
. Furthermore, whenever a class implements __toString()
, it automatically implements the interface behind the scenes and there's no need to manually implement it.
class Foo
{
public function __toString(): string
{
return 'foo';
}
}function bar(Stringable $stringable) { /* … */ }bar(new Foo());
bar('abc');
#New str_contains()
function rfc
Some might say it’s long overdue, but we finally don’t have to rely on strpos
anymore to know whether a string contains another string.
Instead of doing this:
if (strpos('string with lots of words', 'words') !== false) { /* … */ }
You can now do this
if (str_contains('string with lots of words', 'words')) { /* … */ }
#New get_debug_type()
function rfc
get_debug_type()
returns the type of a variable. Sounds like something gettype()
would do? get_debug_type()
returns more useful output for arrays, strings, anonymous classes and objects.
For example, calling gettype()
on a class \Foo\Bar
would return object
. Using get_debug_type()
will return the class name.
A full list of differences between get_debug_type()
and gettype()
can be found in the RFC.
#New get_resource_id()
function pr
Resources are special variables in PHP, referring to external resources. One example is a MySQL connection, another one a file handle.
Each one of those resources gets assigned an ID, though previously the only way to know that id was to cast the resource to int
:
$resourceId = (int) $resource;
PHP 8 adds the get_resource_id()
functions, making this operation more obvious and type-safe:
$resourceId = get_resource_id($resource);
#Abstract methods in traits improvements rfc
Traits can specify abstract methods which must be implemented by the classes using them. There’s a caveat though: before PHP 8 the signature of these method implementations weren’t validated. The following was 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 validation when using a trait and implementing its abstract methods. This means you’ll need to write this instead:
class UsesTrait
{
use Test; public function test(int $input): int
{
return $input;
}
}
#Type annotations for internal functions externals
Lots of people pitched in to add proper type annotations to all internal functions. This was a long standing issue, and finally solvable with all the changes made to PHP in previous versions. This means that internal functions and methods will have complete type information in reflection.
#ext-json
always available rfc
Previously it was possible to compile PHP without the JSON extension enabled, this is not possible anymore. Since JSON is so widely used, it’s best developers can always rely on it being there, instead of having to ensure the extension exist first.
#Breaking changes
As mentioned before: this is a major update and thus there will be breaking changes. The best thing to do is take a look at the full list of breaking changes over at the UPGRADING document.
Many of these breaking changes have been deprecated in previous 7.* versions though, so if you’ve been staying up-to-date over the years, it shouldn’t be all that hard to upgrade to PHP 8.
#Reclassified engine warnings rfc
Lots of errors that previously only triggered warnings or notices, have been converted to proper errors. The following warnings were changed.
- Undefined variable:
Error
exception instead of notice - Undefined array index: warning instead of notice
- Division by zero:
DivisionByZeroError
exception instead of warning - Attempt to increment/decrement property ‘%s’ of non-object:
Error
exception instead of warning - Attempt to modify property ‘%s’ of non-object:
Error
exception instead of warning - Attempt to assign property ‘%s’ of non-object:
Error
exception instead of warning - Creating default object from empty value:
Error
exception instead of warning - Trying to get property ‘%s’ of non-object: warning instead of notice
- Undefined property: %s::$%s: warning instead of notice
- Cannot add element to the array as the next element is already occupied:
Error
exception instead of warning - Cannot unset offset in a non-array variable:
Error
exception instead of warning - Cannot use a scalar value as an array:
Error
exception instead of warning - Only arrays and
Traversables
can be unpacked:TypeError
exception instead of warning - Invalid argument supplied for foreach():
TypeError
exception instead of warning - Illegal offset type:
TypeError
exception instead of warning - Illegal offset type in isset or empty:
TypeError
exception instead of warning - Illegal offset type in unset:
TypeError
exception instead of warning - Array to string conversion: warning instead of notice
- Resource ID#%d used as offset, casting to integer (%d): warning instead of notice
- String offset cast occurred: warning instead of notice
- Uninitialized string offset: %d: warning instead of notice
- Cannot assign an empty string to a string offset:
Error
exception instead of warning - Supplied resource is not a valid stream resource:
TypeError
exception instead of warning
#The @ operator no longer silences fatal errors
It’s possible that this change might reveal errors that again were hidden before PHP 8. Make sure to set display_errors=Off
on your production servers!
#Default error reporting level
It’s now E_ALL
instead of everything but E_NOTICE
and E_DEPRECATED
. This means that many errors might pop up which were previously silently ignored, though probably already existent before PHP 8.
#Default PDO error mode rfc
From the RFC: The current default error mode for PDO is silent. This means that when an SQL error occurs, no errors or warnings may be emitted and no exceptions thrown unless the developer implements their own explicit error handling.
This RFC changes the default error will change to PDO::ERRMODE_EXCEPTION
in PHP 8.
#Concatenation precedence rfc
While already deprecated in PHP 7.4, this change is now taken into effect. If you’d write something like this:
echo "sum: " . $a + $b;
PHP would previously interpret it like this:
echo ("sum: " . $a) + $b;
PHP 8 will make it so that it’s interpreted like this:
echo "sum: " . ($a + $b);
#Reflection method signature changes
Three method signatures of reflection classes have been changed:
ReflectionClass::newInstance($args);
ReflectionFunction::invoke($args);
ReflectionMethod::invoke($object, $args);
Have now become:
ReflectionClass::newInstance(...$args);
ReflectionFunction::invoke(...$args);
ReflectionMethod::invoke($object, ...$args);
The upgrading guide specifies 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);
#Other deprecations and changes
During the PHP 7.* development, several deprecations were added that are now finalised in PHP 8.
- Deprecations in PHP 7.2
- Deprecations in PHP 7.3
- Deprecations in PHP 7.4
- Locale-independent float to string cast