In this post we’re covering several changes and features that should be added to the language with the final release of PHP 7.4:
Forget array_merge: PHP 7.4 Brings Spread Operator in Array Expression
Available since PHP 5.6, argument unpacking is a syntax for unpacking arrays and Traversables into argument lists. To unpack an array or a Traversable, it has to be prepended by … (3 dots), as shown in the following example:
function test(...$args) { var_dump($args); } test(1, 2, 3);
Now this PHP 7.4 RFC proposes to extend this feature to array definitions:
$arr = [...$args];
The first declared benefit of Spread Operator in array expression is all about performance. In fact, the RFC doc states:
Spread operator should have better performance than array_merge. That’s not only because the spread operator is a language structure while array_merge is a function, but also because compile time optimization can be performant for constant arrays.
A significant advantage of Spread operator is that it supports any traversable objects, while the array_merge function only supports arrays.
Here is an example of argument unpacking in array expression:
$parts = ['apple', 'pear']; $fruits = ['banana', 'orange', ...$parts, 'watermelon']; var_dump($fruits);
If you run this code with in PHP 7.3 or earlier, PHP throws a Parse error:
Parse error: syntax error, unexpected '...' (T_ELLIPSIS), expecting ']' in /app/spread-operator.php on line 3
Instead, PHP 7.4 would return an array:
array(5) { [0]=> string(6) "banana" [1]=> string(6) "orange" [2]=> string(5) "apple" [3]=> string(4) "pear" [4]=> string(10) "watermelon" }
The RFC states that we can expand the same array multiple times. Moreover, we can use the Spread Operator syntax everywhere in the array, as normal elements can be added before or after the spread operator. So the following code will work as we may expect:
$arr1 = [1, 2, 3]; $arr2 = [4, 5, 6]; $arr3 = [...$arr1, ...$arr2]; $arr4 = [...$arr1, ...$arr3, 7, 8, 9];
It’s also possible to unpack arrays returned by a function directly into a new array:
function buildArray(){ return ['red', 'green', 'blue']; } $arr1 = [...buildArray(), 'pink', 'violet', 'yellow'];
PHP 7.4 outputs the following array:
array(6) { [0]=> string(3) "red" [1]=> string(5) "green" [2]=> string(4) "blue" [3]=> string(4) "pink" [4]=> string(6) "violet" [5]=> string(6) "yellow" }
We can also use the generator syntax:
function generator() { for ($i = 3; $i <= 5; $i++) { yield $i; } } $arr1 = [0, 1, 2, ...generator()];
But we are not allowed to unpack arrays passed by reference. Consider the following example:
$arr1 = ['red', 'green', 'blue']; $arr2 = [...&$arr1];
If we’d try to unpack an array by reference, PHP throws the following Parse error:
Parse error: syntax error, unexpected '&' in /app/spread-operator.php on line 3
Anyway, if the elements of the first array are stored by reference, they are stored by reference in the second array, as well. Here is an example:
$arr0 = 'red'; $arr1 = [&$arr0, 'green', 'blue']; $arr2 = ['white', ...$arr1, 'black'];
And here is what we get with PHP 7.4:
array(5) { [0]=> string(5) "white" [1]=> &string(3) "red" [2]=> string(5) "green" [3]=> string(4) "blue" [4]=> string(5) "black" }
The Spread operator proposal passed with 43 to 1 votes.
Arrow Functions 2.0 (Short Closures)
In PHP, anonymous functions are considered to be quite verbose and difficult to implement and maintain. This RFC proposes the introduction of the shorter and clearer syntax of the arrow functions (or short closures), that should allow us to clean up significantly our PHP code.
Consider the following example:
function cube($n){ return ($n * $n * $n); } $a = [1, 2, 3, 4, 5]; $b = array_map('cube', $a); print_r($b);
PHP 7.4 allows to use a more concise syntax, and the function above could be rewritten as follows:
$a = [1, 2, 3, 4, 5]; $b = array_map(fn($n) => $n * $n * $n, $a); print_r($b);
Currently, anonymous functions (closures) can inherit variables defined in the parent scope thanks to the use language construct, as shown below:
$factor = 10; $calc = function($num) use($factor){ return $num * $factor; };
But with PHP 7.4, variables defined in the parent scope are implicitly captured by value (implicit by-value scope binding). So we can write the whole function seen above on a single line:
$factor = 10; $calc = fn($num) => $num * $factor;
The variable defined in the parent scope can be used in the arrow function exactly as if we were using use($var), and it’s not possible to modify a variable from the parent scope.
The new syntax is a great improvement to the language as it allows us to build more readable and maintainable code. We can also use parameter and return types, default values, variable-length argument lists (variadic functions), we can pass and return by reference, etc. Finally, short closures can also be used in class methods, and they can make use of the $this variable just like regular closures.
This RFC has been approved with 51 to 8 votes, so we can expect it to be part of PHP 7.4 additions.
Null Coalescing Assignment Operator
Added with PHP 7, the coalesce operator (??) comes in handy when we need to use a ternary operator in conjunction with isset(). It returns the first operand if it exists and is not NULL. Otherwise, it returns the second operand. Here is an example:
$username = $_GET['user'] ?? ‘nobody';
What this code does is pretty straightforward: it fetches the request parameter and sets a default value if it doesn’t exist. The meaning of that line is clear, but what if we had much longer variable names as in this example from the RFC?
$this->request->data['comments']['user_id'] = $this->request->data['comments']['user_id'] ?? 'value';
In the long run, this code could be a bit difficult to maintain. So, aiming to help developers to write more intuitive code, this RFC proposes the introduction of the null coalescing assignment operator (??=). So, instead of writing the previous code, we could write the following:
$this->request->data['comments']['user_id'] ??= ‘value’;
If the value of the left-hand parameter is null, then the value of the right-hand parameter is used.
Note that, while the coalesce operator is a comparison operator, ??= is an assignment operator.
This proposal has been approved with 37 to 4 votes.
Typed Properties 2.0
Argument type declarations, or type hints, allow to specify the type of a variable that is expected to be passed to a function or a class method. Type hints are a feature available since PHP 5, and since PHP 7.2 we can use them with the object data type. Now PHP 7.4 brings type hinting a step forward by adding support for first-class property type declarations. Here is a very basic example:
class User { public int $id; public string $name; }
All types are supported, with the exception of void and callable:
public int $scalarType; protected ClassName $classType; private ?ClassName $nullableClassType;
The RFC explains the reason why void and callable are not supported:
The void type is not supported, because it is not useful and has unclear semantics.
The callable type is not supported, because its behavior is context dependent.
So we can safely use bool, int, float, string, array, object, iterable, self, parent, any class or interface name, and nullable types (?type).
Types can be used on static properties:
public static iterable $staticProp;
They are also allowed with the var notation:
var bool $flag;
It’s possible to set default property values, which of course must match the declared property type, but only nullable properties can have a default null value:
public string $str = "foo"; public ?string $nullableStr = null;
The same type applies to all properties in a single declaration:
public float $x, $y;
What happens if we make an error on the property type? Consider the following code:
class User { public int $id; public string $name; } $user = new User; $user->id = 10; $user->name = [];
In the code above, we declared a string property type, but we set an array as property value. In such scenario, we get the following Fatal error:
Fatal error: Uncaught TypeError: Typed property User::$name must be string, array used in /app/types.php:9
This RFC has been approved with 70 to 1 votes.
Weak References
With this RFC, PHP 7.4 introduces the WeakReference class, which allows programmers to retain a reference to an object that doesn’t prevent the object itself from being destroyed.
Currently PHP supports Weak References by using an extention like pecl-weakref. Anyway, the new API is different from the documented WeakRef class.
Here is an example from the author of this proposal, Nikita Popov:
$object = new stdClass; $weakRef = WeakReference::create($object); var_dump($weakRef->get()); unset($object); var_dump($weakRef->get());
The first var_dump prints object(stdClass)#1 (0) {}, while the second var_dump prints NULL, as the referenced object has been destroyed.
Nikita Popov@nikita_ppv
Slides for my PHP 7.4 talk at #PHPRussia2019. Was a great conference!https://www.slideshare.net/nikita_ppv/typed-properties-and-more-whats-coming-in-php-74 …
LinkedIn SlideShare @SlideShare
293
2:07 AM - May 20, 2019
Twitter Ads info and privacy
129 people are talking about this
This RFC passed with 28 to 5 votes.
Covariant Returns and Contravariant Parameters
Variance is a property of class hierarchies describing how the types of a type constructor affect subtypes. In general, a type constructor can be:
Invariant: if the type of the super-type constrain the type of the subtype.
Covariant: if the ordering of types is preserved (types are ordered from more specific to more generic).
Contravariant: if it reverses the order (types are ordered from more generic to more specific).
Currently, PHP has mostly invariant parameter and return types, with few exceptions. This RFC proposes to allow covariance and contravariance on parameter types and return types, also providing several examples of code.
Here is an example of covariant return type:
interface Factory { function make(): object; } class UserFactory implements Factory { function make(): User; }
And here is an example of contravariant parameter type:
interface Concatable { function concat(Iterator $input); } class Collection implements Concatable { // accepts all iterables, not just Iterator function concat(iterable $input) {/* . . . */} }
See the RFC for a closer look at covariance and contravariance in PHP 7.4.
This RFC passed with 39 to 1 votes.
Preloading
This proposal from Dmitry Stogov is one of our favorite because it should bring a significant boost in performance. Preloading is the process of loading libraries and frameworks into the OPCache at module initialization (read more about PHP lifecycle).
PHP lifecycle (Image source: PHP Internals)
Here is how preloading works in the words of Dmitry:
On server startup – before any application code is run – we may load a certain set of PHP files into memory – and make their contents “permanently available” to all subsequent requests that will be served by that server. All the functions and classes defined in these files will be available to requests out of the box, exactly like internal entities.
These files are loaded on server startup, are executed before any application and stay available for any future requests. That’s great in terms of performance.
Preloading is controlled by a specific php.ini directive: opcache.preload. This directive specifies a PHP script to be compiled and executed at server start-up. This file can be used to preload additional files, either including them or via the opcache_compile_file() function (read more on PHP documentation).
But there’s a downside. In fact, the RFC explicitly states:
preloaded files remain cached in opcache memory forever. Modification of their corresponding source files won’t have any effect without another server restart.
However, all functions defined in preloaded files will be permanently loaded into PHP function and class tables, and remain available for every future request. This will lead to good performance improvements, even if these improvements could be considerably variable.
You can read more about the limitations and exceptions of preloading on the official Preloading RFC page.
New Custom Object Serialization Mechanism
This is another proposal from Nikita Popov approved with a large majority of votes.
We grew our traffic 1,187% with WordPress.
We’ll show you how.
Join 20,000+ others who get our weekly newsletter with insider WordPress tips!
SUBSCRIBE NOW
Subscribe to the Kinsta newsletter
SUBSCRIBE
I agree to the Terms and Conditions and Privacy Policy
Currently, we have two different mechanisms for custom serialization of objects in PHP:
The __sleep() and __wakeup() magic methods
The Serializable interface
According to Nikita, both these options have issues that lead to complex and unreliable code. You can dive deep into this topic in the RFC. Here I just mention that the new serialization mechanism should prevent these issues by providing two new magic methods, __serialize() and __unserialize(), that combine the two existing mechanisms.
This proposal passed with 20 to 7 votes.
Deprecations
The following functions/functionalities will be deprecated with PHP 7.4. For a more comprehensive list of deprecations, check out PHP 7.4 Upgrade Notes.
Change the Precedence of the Concatenation Operator
Currently, in PHP the “+” and “-” arithmetic operators, and the “.” string operator are left associative and have the same precedence (read more about Operator Precedence).
As an example, consider the following line:
echo "sum: " . $a + $b;
In PHP 7.3 this code produces the following warning:
Warning: A non-numeric value encountered in /app/types.php on line 4
This because the concatenation is evaluated from left to right. It’s the same as writing the following code:
echo ("sum: " . $a) + $b;
This RFC proposes to change the precedence of operators, giving “.” a lower precedence than “+” and “-” operators, so that additions and subtractions would always be performed before the string concatenation. That line of code should be equivalent to the following:
echo "sum: " . ($a + $b);
This is a two-step proposal:
Starting from version 7.4, PHP should emit a deprecation notice when encountering an unparenthesized expression with “+”, “-” and “.”.
The actual change of precedence of these operators should be added with PHP 8.
Both proposals have been approved with a large majority of votes.
Deprecate left-associative ternary operator
In PHP the ternary operator, unlike many other languages, is left-associative. According to Nikita Popof, this can be confusing for programmers who switch between different languages.
Currently, in PHP the following code is correct:
$b = $a == 1 ? 'one' : $a == 2 ? 'two' : $a == 3 ? 'three' : 'other';
It’s interpreted as:
$b = (($a == 1 ? 'one' : $a == 2) ? 'two' : $a == 3) ? 'three' : 'other';
And this could lead to errors because it may not be what we intend to do. So this RFC proposes to deprecate and remove the use of left-associativity for ternary operators and force developers to use parentheses.
This is another two-step proposal:
Starting from PHP 7.4, nested ternaries without explicit use of parentheses will throw a deprecation warning.
Starting from PHP 8.0, there will be a compile runtime error.
This proposal has been approved with 35 to 10 votes.