Cover image for All the changes that come with PHP 7.4

All the changes that come with PHP 7.4

xoubaman profile image Carlos Gándara Updated on ・8 min read

PHP 7.4 is available since past 28th of November. This new update for PHP 7 version comes with some nice additions, another minor features and a bunch of deprecations.

In this post, we will review all the changes in the version, one RFC at a time.

The fancy additions

Some awaited features are finally here with 7.4. Following is the list of the ones I think have a bigger impact for the language.

Typed properties

Class properties can be typed.

Here is a quick tour:

class Color {}
class Profession {}

class Guy {
    public string $name;
    private int $id;
    public static int $age;
    public Color $hairColor;
    public ?Profession $profession;
    public string $address = 'Discworld';
    public string $phone;

class InheritingGuy extends Guy{
    public int $name; //Error, types are invariant
    public string $id; //Unless the visibility changes from private to a less restrictive one

$guy = new Guy();
$guy->name = 'Sherlok'; //Good!
$guy::$age = 42; //Static properties are typed too
$guy::$age = '42'; //If strict_types is enabled this assigment throws a TypeError, if not PHP will try to convert fom string to int
$guy->hairColor = new \DateTimeImmutable(); //Throws TypeError, we can only assign a Color
$guy->profession = null; //Possible because it is nullable
echo $guy->address; //This is ok because it has a value
echo $guy->phone; //Throws an error because value was not initialized

The next natural step in improving the language type system. The above example covers several scenarios when using typed properties, but I strongly recommend to read the RFC to fully understand all the nuts and bolts of typed properties.

Arrow functions

Enable a simpler syntax for anonymous functions.

The syntax removes the need for the use keyword to import variables in the function scope, reducing substantially the size of simple functions. Especially handy for the array_map, filter and reduce methods.

$template = ', I choose you!'.PHP_EOL;

$oldStyle = function($who) use ($template) {
    return $who.$template;

$withArrowFunctions = fn($who) => $who.$template;

$name = 'Pikachu';
echo $oldStyle($name);
echo $withArrowFunctions($name);

//Both calls print "Pikachu, I choose you!"

Covariant returns and contravariant parameters

When overriding a method it will be possible to define more specific return types and to define less specific types of parameters.

Covariance and contravariance are tricky to explain, but actually not that hard. So far, in PHP we have invariant parameters and return types. This will change with PHP 7.4. Let's illustrate it with an example:

class MarioEnemy {}
class Koopa extends MarioEnemy {}

class EnemyType {}
class Turtle extends EnemyType {}
class Mushroom extends EnemyType {}

interface MarioEnemyGrabber {
    public function grabEnemyOfType(Turtle $type): MarioEnemy;

Previous to 7.4 you cannot extend the interface changing the method's signature because parameters and return types are invariant. With 7.4 we can have more specific return types and less specific parameter types:

//Overriding return type with a more specific one
interface OneKoopaGrabber extends MarioEnemyGrabber {
    public function grabEnemyOfType(Turtle $type): Koopa;
//Overridign parameter type with a less specific one
interface AnotherKoopaGrabber extends MarioEnemyGrabber {
    public function grabEnemyOfType(EnemyType $type): MarioEnemy;

When this post was published it had an example of covariant parameters, suggesting it would be possible to provide more specific types for parameters when overriding methods. That is not correct and I have removed the example. Thanks to @goceb for pointing it in the comments, you can check there a similar example of what cannot be done.

Null coalescing assignment operator

The null coalescing assignment operator ??= is available.

//Keeps $discount if it is not null, assigns 20 otherwise
$discount ??= 20;

//Before we had to do
$discount = $discount ?? 20;

Another step forward to reduce noise when assigning values that might not be there.

Spread operator in arrays

An array (or a Traversable) can be spread inside another array.

$blueGreen = ['blue', 'green'];
$andAlsoRed = ['red', ...$blueGreen];
//result is ['red', 'blue', 'green']

Note this is not a replacement for array_merge. It can be more handy in some situations, but not usable in others.


Allow to preload the application code, removing some overhead of recompilation code in each request. Average performance gain is around 10 to 15%

With OpCache almost all the overhead of PHP runtime compilation is mitigated, but there are still some parts that can be further optimized. This is what preloading is about, with the trade-off of requiring a server restart for any change in the code to be detected and the need for a file defining some classes that must be excluded from preloading, because not all can be.

This is a low-level feature and quite hard to explain in an easy way, at least for my understanding of it (not a system guy or a PHP Core Member). I recommend reading the RFC and watching this talk by Nikita Popov in the past PHP Barcelona Conference, explaining in a somehow accessible way what is the benefit over OpCache and plenty of other interesting stuff, not only preloading related.

Other additions

Here is a list of other less shiny additions to 7.4.

Foreign Function Interface

It is possible to run C code from within PHP.

$ffi = FFI::cdef("here we declare C structs and methods");

//Creating a struct from the C code in the PHP code
$myStruct = new $ffi->new ('struct my_struct'); 
echo $myStruct->some_property;

//Calling functions from the C code in the PHP code
echo $ffi->some_method();

This is complex stuff, aiming for fast prototyping. Some writing on the web claim it will allow to load extensions with Composer instead of installing them via PECL. For what is said in the RFC, it is more a first step that may or may not allow such thing in the future.

Numeric literal separator

An underscore will be supported as number separator.

So we can write 1_000.50 instead of 1000.50. Useful to improve readability of big numbers, ten millions is better to identify as 10_000_000 than as 10000000.

Weak references

A WeakReference will be implemented in the core of the language. Before there were extensions implementing it, but with a number of issues or unusable after PHP 7.3.

To be honest weak references concept is new to me and after searching and reading about it for a while I still don't get for what they are useful. All the examples I can find are basically the same as the one in PHP documentation that looks quite irrelevant to me. If you know a practical use case please share it in the comments.

Allow to throw exception inside __toString() method

Pretty self-explanatory. It was not possible due to some PHP internals weirdness.

Notice for non valid array container

Accessing as an array something that cannot be accessed that way raises a warning.

Until 7.4, when we do so we get NULL and that's it.

$notArrayAccessible = 123;
$notArrayAccessible[0]; //throws a warning, integer cannot be accessed as arrays

Warning when providing invalid input to base_convert()

Invalid input for base_convert and similar functions will raise a warning and the method supports negative values.

The base_convert method changes numbers between bases (decimal, hexadecimal, binary, etc.), along with concrete representation methods like decbin. However, these methods silently ignore invalid input like passing 42 as a binary value.

Include hash extension in the language core

The hash extension will be always enabled.

Improve openssl_random_pseudo_bytes()

Fix an inconsistency with the function openssl_random_pseudo_bytes().

Sometimes calling this function enforces userland checks for errors. Now it will throw an exception and the confusing second parameter is marked as deprecated and will be removed in PHP 8.

str_split() supporting multibyte

The method mb_str_split allows multibyte support for splitting strings.

Reflection for references

The class RefelctionReference class allow detecting references between variables.

Tools requiring this kind of information had to use some hacky techniques. Not anymore.

New custom object serialization mechanism

Two new magic methods __serialize() and __unserialize for object serialization.

Previous serialization mechanism using Serializable interface and __sleep() and __wakeup() magic methods, had a number of issues with complex object graphs that are solved with the new magic methods.

It is a tricky topic well explained in the RFC, so go for it to know the details.

Password Hash Registry

Function password_algos() allows retrieving the available password hashing algorithms.

Support for argon2i and argon2id hashing when no ext/sodium installed

Hashing with argon2i and argon2id is possible when ext/sodium is not enabled in the core thanks to the password registry hash introduced in 7.4.

Escape PDO "?" parameter placeholder

It is possible to escape the ? character in PDO using ??.

Some PostgreSQL features were not possible to use with the PDO extension because they used ? as part of their syntax. Now PDO parses ?? as a single ?, allowing these PostgreSQL operations and any others using question marks.


Several parts of the language have been marked as deprecated and some extensions moved out.

Small deprecations

A number of language constructs and methods are marked as deprecated, raising a E_DEPRECATED warning when used.

Nothing especially remarkable since most of the deprecations are based on inconsistencies and clean ups. The list of deprecations:

  • The real type
  • Magic quotes legacy
  • array_key_exists() with objects
  • Reflection export() methods
  • mb_strrpos() with encoding as 3rd argument
  • implode() parameter order mix
  • Unbinding $this from non-static closures
  • hebrevc() function
  • convert_cyr_string() function
  • money_format() function
  • ezmlm_hash() function
  • restore_include_path() function
  • allow_url_include ini directive

Deprecate curly braces array access

Accessing arrays with curly braces will emit a deprecation warning in order to be removed in PHP 8.

Funny since the ability to use curly braces to access array elements
is pretty much unknown and has very little usage in the wild.

$myArray = [1,2,3];
echo $myArray{1}; //Will trigger a deprecation warning 

Enforce parentheses for chained ternary operators

Nested ternaries will require parentheses since they will be right-associative instead of left-associative. Not doing so will raise a deprecation warning.

1 ? 2 : 3 ? 4 : 5;   // deprecated
(1 ? 2 : 3) ? 4 : 5; // ok
1 ? 2 : (3 ? 4 : 5); // ok

Nested ternary operators are quite hard to read anyway, so you can just don't use them straight :P

Change the precedence of the concatenation operator

Raise a deprecation error when using an expression with operator . and + or - without parentheses.

In PHP 8 the precedence of . will be less than + or -.

echo "result: " . $a + $b;

In PHP 7.3 this concatenates doing the sum afterwards as in ("result: " . $a) + $b;.
In PHP 7.4 it will raise a deprecation warning if no parentheses.
In PHP 8 this will do the sum, then concatenate as in ("result: " . $a) + $b;.

Weird fact: I could not reproduce this behavior in a test script ¯\(ツ)

Removed extensions

A number of extensions have been unbundled from the language:

  • ext/interbase: this extension to support InterBase database has serious implementation issues and has been moved to PECL RFC
  • ext/wddx: this data format intended for exchanging data had its own extension, now deprecated and moved to PECL RFC
  • ext/recode: an extension to convert between charsets. It is old, discontinued, and iconv and mbstring extensions are doing the same in PHP RFC

Posted on by:

xoubaman profile

Carlos Gándara


I do software, mainly with PHP. Also I read about it. And I think about it.


Editor guide

Unfortunately, only contravariant arguments are supported so the command/handler example will not work:

interface Command{}

interface CommandHandler{
    public function handle(Command $command) : void ;

class KillAllHumans implements Command{}

class KillAllHumansHandler implements CommandHandler{
    public function handle(KillAllHumans $command) : void {}

You are right. Amazingly enough, I realized it in a conversation in some slack community at the same time you wrote the comment.

I will correct the article as soon as I can. Update: fixed!

Thanks for pointing it out!


I can't see the usefulness of contravariant VS covariant arguments. I guess there were some technical issues implementing covariant arguments.

I was hyped up with PHP 7.4 only for that!


An excellent break down of the new feature set Carlos.


Thanks! Glad you liked it :)


Weak references is related to garbage collector basically, there are good examples of how to use it in nodejs and java. It helps the garbage collector identify what it can get, not when, but which objects/vars and etc...

It's a kind of feature that will help projects like doctrine, that can consume a lot of resources because of the references through objects and so on.

Did you get it or it was confused?


Yup, I got the concept but couldn't find a real-life example. I guess I never had to deal with such type of functionality in any project, like holding big graphs of object relations.

Funny enough, Ocramius, a Doctrine core maintainer, voted against weak references feature 🤷‍♂️

Thanks for the comment!


Thanks for the article Carlos.
I‘m currently learning PHP and something intrigued me, why did you write
if(!($command instanceOf RegisterUserCommand)){} as if it would be the same as passing RegisterUserCommand as a parameter? Doesn‘t the exclamation inside the if statement mean „only if $command is not an instance of RegisterUserCommand“?


Wow first off that elephant pic is 💯🤩
Great article 👐👐


I fixed an error in the underscore for numeric literals, the correct way is to use a dot for separating the decimal part. So 1000.50 is 1_000.50, not 1_000_50.


thanks, awesome article!


Oh nevermind, I see what you mean now. In my head you meant to check if it is an instance in the first place , but you are looking for the case that it isn’t in order to throw an Exception.


Exactly. But not needed anymore :D

UPDATE: Actually no, the example was not correct :(


Actually, my assumption was not correct, I have removed the example. Sorry for the confusion.


Well explained, well done


Great Article Man!❤✨ Loved it!