PHP 8 Changes [what is next?]
Coding (PHP 8)
Discover the whole list of changes to migrate to PHP 8
Introduction
In the last couple of episodes, we went through several topics, such as what are the best addition to PHP 8 and also a deep explanation of the new features.
In this episode of the series, we will dive into the changes that will take place in the new version of the language.
Understanding changes is significant because we need to be aware of possible backwards-incompatible changes that can make or break your web application.
Also by analyzing what are the changes we can forecast where the language is going and what will happen during the years to come.
Let’s jump into it!
A look into PHP 8 the series
The goal of this series is to get you ready to work with the latest version of this beautiful backend language.
The episode of the series are (in chronological order):
- September's updates the collection of all the most important features and changes a month before they arrive at our machine
- New Features the list with explanation of all the new toys we will have in our arsenal
- Changes Discover all the new improvements, syntax fixes and new features of PHP 8
- JIT We have heard about JIT for a long time and it almost among us, learn all there is to know about it
Table of Content
New features
Syntax
Attributes, commonly known as annotations
Constructor property promotion
Trailing comma in parameter lists
Operations
Behaviours
Inheritance with private methods
Create DateTime objects from interface
Abstract methods in traits improvements
implementation of token_get_all
Changes
The @ operator no longer silences fatal errors
Stricter type checks for arithmetic and bitwise operators
Namespaced names being a single token
Safer string to number comparisons
Reflection method signature changes
Fatal error for incompatible method signatures
Method signature changes on reflection classes
Two different things to explain in this section,
What is a method signature and what are reflections?
Let’s start with the first.
In programming, a method signature is a part of the method declaration.
It is the combination of the method name and the number and orders of the parameters.
public function setCoordinates(int xCoordinate, int yCoordinate) {}
In this case, the name of the function, the fact that there are 2 parameters and the X comes before Y one is all part of the method signature.
What about reflection?
I have already written about reflection in my article about abstract classes and the extra of OOP.
But to sum-up reflection API adds the ability to introspect classes, interfaces, functions, methods and extensions.
These APIs are made of several classes and in PHP 8 the behaviour of some of them have changed.
ReflectionClass::newInstance($arguments); ReflectionFunction::invoke($arguments); ReflectionMethod::invoke($object, $arguments);
Here is how you used to invoke the methods above for these classes.
ReflectionClass::newInstance($argument = null, ...$arguments); ReflectionFunction::invoke($argument = null, ...$arguments); ReflectionMethod::invoke($object, $argument = null, ...$arguments);
As you can see the ‘…’ syntax for variable arguments has been added.
A little explanation here is due as well:
from PHP 5.6 onwards, the argument lists may include the ... token to specify that the function accepts a variable number of arguments.
The arguments will be passed into the given variable as an array.
You can use func_get_args() to get them.
Make sorting stable
If you use sorting function often, you might have realized that when evaluating equal elements the sorting gets unstable.
Here is what I mean:
$array = [ 'c' => 1, 'd' => 1, 'a' => 0, 'b' => 0, ]; asort($array); ['b' => 0, 'a' => 0, 'c' => 1, 'd' => 1] ['a' => 0, 'b' => 0, 'd' => 1, 'c' => 1] ['b' => 0, 'a' => 0, 'd' => 1, 'c' => 1]
The result, even though sorted, seems to return ‘random’ values when the elements are the same.
And this behaviour is consistent for all the sorting functions including sort, rsort, usort, asort, arsort, uasort, ksort, krsort, uksort, array_multisort, plus the methods on ArrayObject.
In PHP 8 the code will do something similar to this:
function stable_usort(array &$array, callable $compare) { $arrayAndPos = []; $pos = 0; foreach ($array as $value) { $arrayAndPos[] = [$value, $pos++]; } usort($arrayAndPos, function($a, $b) use($compare) { return $compare($a[0], $b[0]) ?: $a[1] <=> $b[1]; }); $array = []; foreach ($arrayAndPos as $elem) { $array[] = $elem[0]; } }
This function explicitly stores the original order of the elements and uses it as a fallback comparison criterion.
Incompatible method signatures generate a fatal error
When leveraging inheritances among your OOP applications you need to be sure that attributes match the types of the variable they are supposed to be.
Since PHP 5.0 the method signature check generated a strict standards notice.
This was raised to a warning in PHP 7.0. because it will be upgraded to a fatal error in the future.
This is true only for classes but not for Interfaces;
Look at the example below:
interface Car { public function drive(array $speed); } class Fiat implements Car { public function drive(int $speed) {} } // Fatal error: Declaration of Fiat::drive(int $speed) must be compatible with Car::drive(array $speed) class Player { public function jump(array $height) {} } class Goalkeeper extends Player { public function jump(int $height) {} } // Warning: Declaration of Goalkeeper::jump(int $height) should be compatible with Player::jump(array $height)
The difference is that the first one comes from an interface, instead, the second example comes from a class.
A fatal error is thrown if an abstract method of the same name happens anywhere higher in the inheritance hierarchy.
The proposal and the update we are going to see in PHP 8 is to always throw a fatal error on incompatible method signatures.
Consistent type errors
One of the many differences between user-defined functions and internal functions is the way they manage some errors.
In case we pass the wrong type, our function results in a TypeError, whereas internal functions have a weird behaviour.
What they do is to throw a warning and return null.
Have a look at the examples below.
function getCosine(float $argument) {} getCosine('this is a string'); // TypeError: Argument 1 passed to getCosine() must be of the type float, string given
This is what we expect from a user-defined function and it works well and it is self-explanatory.
What happens if alternatively, we decide to use the core PHP function?
var_dump(cos(new stdClass)); // Warning: cos() expects parameter 1 to be float, object given // NULL
That could be fine until ...
Do you want to see something strange?
var_dump(DateTime::createFromFormat(new stdClass, "this is a string")); // Warning: DateTime::createFromFormat() expects parameter 1 to be string, object given // bool(false)
This is the same error but in this case, the function returns a false value instead of a null.
It happens for about 150 PHP functions.
You can easily understand that it makes it impossible to check for an error in the code if you do not know what type of variable is returned beforehand.
What this RFC proposed is to make this behaviour consistent and always return TypeError exceptions.
You can read more about this RCF in the consistent type errors official page.
Reclassified engine warnings
Many errors in the past version of the language have an unsuitable severity level.
During the last couple of years, the developers of PHP have been trying to catch up with the latest trend in web development and unfortunately, this aspect of the language has been a bit neglected.
This until now.
From PHP 8 plenty of errors have been reevaluated and they know throw error according to their level of severity.
Here are a few examples:
- Creating default object from empty value was a Warning now is an Error exception;
- Trying to get property of non-object was a Notice now is a Warning;
- Cannot unset offset in a non-array variable was a Warning now is an Error exception;
- Cannot use a scalar value as an array was a Warning now is an Error exception;
- Invalid argument supplied for foreach() was a Warning now is a TypeError exception;
- Illegal offset type was a Warning now is a TypeError exception;
- Illegal offset type in isset or empty was a Warning now is a TypeError exception;
- Illegal offset type in unset was a Warning now is a TypeError exception;
- Array to string conversion was a Notice now is a Warning;
- Undefined variable: Error was a Notice now is an Error exception;
- Undefined array index: was a Notice now is a Warning;
- Division by zero: DivisionByZeroError was a Warning now is an Error exception;
You can have a look at the looooong list of change in the official RFC
The error control operator
Do you remember me writing about the error control operator?
Well, this operator is (better say was) used to ignore any fatal errors that were generated by a PHP expression.
With PHP 8 this feature no longer works so after upgrading errors would be revealed again.
It should not be a problem because the usage of this operator is such a bad practice that nobody ever uses it.
Am I right?
Changed Default error level on error_reporting()
PHP has several levels of errors that can show to us while coding.
We use the error_reporting() function to set the error_reporting directive at runtime.
Using this function change the level for the whole duration of the current script
The default level is now E_ALL.
Be careful when you upgrade because errors that were ignored before might show up.
Change Default PDO Error Mode
In PHP 7.4 and below when a SQL query occurs there are no errors or warnings shown.
This except you specifically overwrites this behaviour.
This is not useful for web developers because the majority of the time they only see errors like “call to fetch() on non-object” which does not explain that the error actually is.
With this proposal in PHP 8, the new default error would be
PDO::ERRMODE_EXCEPTION.
Concatenation precedence
Currently the precedence of the symbols'.', '+' and '-' operators are equal.
Which means that combinations of these operators are simply evaluated from left to right.
In some cases, this can become a big issue.
Let explain it with an example:
echo "sum: " . $a + $b;
What you probably want here is to sum the two variables and prefix them with the string.
But since . and + have the same importance what PHP does instead is this:
// current behavior: evaluated left-to-right echo ("sum: " . $a) + $b;
And here is the problem.
In PHP 8 the concatenation symbol is now a step behind the addition and subtraction symbols.
Like so:
// desired behavior: addition and subtraction have a higher precendence echo "sum :" . ($a + $b);
This feature has been already deprecated in PHP 7.4,
You can see all the details in this part of last year’s article.
Stricter type checks for arithmetic/bitwise operators
Here is another weird thing that happens with PHP.
If you use the % operator on an array, it will convert into the integer zero (0).
There are several crazy episodes like this one.
Just look below;
Using the operators +, -, *, /, **:
- Throw Error exception on array operand. (Excluding + if both operands are array.)
- Silently convert a resource operand to the resource ID as an integer.
- Convert an object operand to integer one, while throwing a notice.
Using the operators %, <<, >>, &, |, ^:
- Silently convert an array operand to integer zero if empty or integer one if non-empty.
- Silently convert a resource operand to the resource ID as an integer.
- Convert an object operand to integer one, while throwing a notice.
Using the operator ~:
- Throw an Error exception for an array, resource and object operands.
Using the operators ++ and --:
- Silently do nothing if the operand is an array, resource or object.
Here is an example:
var_dump([] % [42]); // int(0)
What?
The new behavior proposed by Nikita Popov is the same for all the arithmetic/bitwise operators +, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:
And it will be to throw a TypeError exception for an array, resources and objects.
In case of addition with two arrays, the operands will remain legal.
Treat namespaced names as a single token
PHP distinguishes four kinds of namespaced names:
- Unqualified names like Repository, which coincide with identifiers.
- Qualified names like Admin\Repository.
- Fully qualified names like \Repository.
- Namespace-relative names like namespace\Repository.
PHP currently treats namespaced names like Admin\Repository as a sequence of identifiers and namespace separator tokens.
Each of these kinds will be represented by a distinct token:
Repository; // Before: T_STRING // After: T_STRING // Rule: {LABEL} Admin\Repository; // Before: T_STRING T_NS_SEPARATOR T_STRING // After: T_NAME_QUALIFIED // Rule: {LABEL}("\\"{LABEL})+ \Repository; // Before: T_NS_SEPARATOR T_STRING // After: T_NAME_FULLY_QUALIFIED // Rule: ("\\"{LABEL})+ namespace\Repository; // Before: T_NAMESPACE T_NS_SEPARATOR T_STRING // After: T_NAME_RELATIVE // Rule: "namespace"("\\"{LABEL})+
In PHP 8 namespaced names are treated as a single token, and as such allow reserved keywords to appear inside them.
The first reason is to decrease the backwards compatibility effect of new reserved keyword additions in future versions of PHP.
Think of the new keywords like fn or match.
The second is that by dealing with namespaced names as a single token it will avoid several syntactical vaguenesses.
The backward incompatibility is very little.
In fact, the only applications impacted are the one with code using whitespace or comments between namespace separators.
This will now produce a parse error.
Saner numeric strings
Have you ever heard of the concept of numeric strings?
In PHP numeric strings are strings that can be interpreted as numbers.
A string can be categorised in four ways:
- numeric string is a string containing only a number, optionally preceded by whitespace characters. For example, "123" or " 1.23e2".
- leading-numeric string is a string that begins with a numeric string but is followed by non-number characters (including whitespace characters). For example, "123abc" or "123 ".
- non-numeric string is a string which is neither a numeric string nor a leading-numeric string.
- integer string is stricter than a numeric string as it has the following additional constraints.
The latter does not accept leading whitespace and it does not accept leading zeros.
There are several problems with this, as noted in the official RFC.
Here are the main ones:
- Strings which happen to start with a digit can be interpreted as numbers, which can lead to bugs.
- \is_numeric() is misleading, as it will reject values that a weak-mode parameter check will accept.
- Numeric strings with leading whitespace are considered more numeric than numeric strings with trailing whitespace.
The proposal consists in consolidating the numeric string modes into a single notion.
Make it so that numeric characters are only with both leading and trailing whitespace allowed.
Any other type of string is non-numeric and will throw TypeErrors when used in a numeric context.
This will produce the following behaviors:
function getSomething(int $i) { var_dump($i); } getSomething("123 "); // int(123) getSomething("123abc"); // TypeError var_dump(is_numeric("123 ")); // bool(true) $string = 'The world'; var_dump($string['4str']); // string(1) "w" with E_WARNING "Illegal string offset '4str'" var_dump($string['4.5']); // string(1) "w" with E_WARNING "String offset cast occurred" if the secondary vote is accepted otherwise TypeError var_dump($string['string']); var_dump(123 + "123 "); // int(246) var_dump(123 + "123abc"); // int(246) with E_WARNING "A non-numeric value encountered" var_dump(123 + "string"); // TypeError var_dump(123 & "123 "); // int(123) var_dump(123 & "123abc"); // int(123) with E_WARNING "A non-numeric value encountered" var_dump(123 & "abc"); // TypeError
Be aware,
there will be some incompatibility issue with PHP 7.4 and below.
They are:
- Code relying on numerical strings with trailing whitespace to be considered non-well-formed.
- Code with liberal use of leading-numeric strings will need to use explicit type casts.
- Code relying on the fact that an empty string evaluates to 0 for arithmetic/bitwise operations.
Safer string to number comparisons
Like the above paragraph, we are still talking about casting numbers here.
The proposal was to create better non-strict comparisons that will guarantee less error.
The new version will do that by checking beforehand and using a number comparison only if the string is actually a numeric one.
In case the string is not the number is converted into a string, and a string comparison is performed.
The reason for this is because sometimes PHP behave weirdly while comparing different types of variable.
A famous example is:
0 == "string" // true
The new update affects any operations that perform non-strict comparisons:
- The operators <=>, ==, !=, >, >=, <, and <=.
- The functions in_array(), array_search() and array_keys() with $strict set to false (which is the default).
- The sorting functions sort(), rsort(), asort(), arsort() and array_multisort() with $sort_flags set to SORT_REGULAR (which is the default).
Conclusion
Even this year there have been plenty of updates to the core language, the list of changes above is a clear example.
For years PHP has been considered as a dead language and it seemed at the verge of extinction still, this programming language has given us web developers the possibility to create wonderful applications, not to mention career out of it.
These continuous updates are proof that the language IS MORE ALIVE THAT EVER and it will be one of the best choices in the back-end field for many years to come.
If, after reading about these new changes you want to brush up some basics here is the link to the first episode of the series the Basics of PHP.
If instead, you want to move on to something a bit more challenging have a look at the introduction to Composer.
Also, to be the first one to know when a new article is published do not forget TO SUBSCRIBE TO THE EMAIL LIST BELOW.