2

PHP 8.1: These are the new features

 1 year ago
source link: https://devm.io/php/php-8-update-major-release
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Is the upgrade worth it or should we stick with 7.4 instead?

PHP 8.1: These are the new features

13. Jul 2022


Last year, a new major version of PHP was released. Even though it brought a lot of great features, it wasn't very well received by the PHP community. Version 8.0 didn't manage to completely win over developers - but maybe PHP version 8.1 will. Let’s take a look.

According to Packagist statistics, PHP 7.3, 7.2, and 7.4 are the most widely used versions of the programming language. However, version 7.2 will no longer receive security updates, 7.3 will only receive them until November 2021, and 7.4 will only receive security updates (also starting in November). This means that anyone still using PHP versions 7.2 and 7.3 should switch as soon as possible. In this article, we will take a look at the new version 8.1 features that are already declared “Implemented” in the RFCs.

Deprecated: Warnings for arguments with null values for internal functions

For historical reasons, internal PHP functions allow null as an argument, even though this type of the argument is not nullable. There are some polyfills - functions that mimic internal functions in PHP - that simplify the transition to new versions. These cannot currently map some internal functions correctly. This is because even written functions can only receive a null as a parameter for scalar types if the parameter is also declared as nullable. The same behavior is applied to internal functions in PHP 8.1 - but with a deprecate warning first. The plan for PHP versions beyond 9.0 is to generate a TypeError for these functions. If you simply pass null as a parameter to the internal functions now, then you might have some trouble with your code later.

New array_is_list() function

In PHP, we can easily represent complex structures with an array. We can simply add new array keys and modify existing ones during runtime. However, this flexibility brings performance issues. A PHP array can be optimized if the array keys are numbered from 0 to the number of elements. Optimization is difficult to implement because checking the keys is very time-consuming. Listing 1 shows the current polyfill for the function.

Listing 1

function array_is_list(array $array): bool {
  $expectedKey = 0;
  foreach ($array as $i => $value) {
    if ($i !== $expectedKey) { return false; }
    $expectedKey++;
  }
  return true;
}

Code can be implemented more performantly with the native implementation of the function, since it’s no longer necessary to assume that the array keys will have a non-numeric value. This will make PHP even more performant in the future. You could potentially also think about a new data type list and handle it separately. Maybe it could even become an RFC.

Restrict $GLOBALS as reference

Global variables have existed in PHP since the beginning and provide a lot of flexibility. However, this flexibility often has some side effects. The RFC is intended to ban assigning $GLOBALS variables as a reference to another variable. In order to understand the RFC, let’s take a look at the following example:

$a = 1;
$GLOBALS['a'] = 2;
var_dump($a); // int(2)

Here, the variable $a is stored internally as a compiled variable for faster access to variables. In order to allow $a to be modified with Globals, the array keys of Globals are stored in such a way that the keys point to the variables. However, the $GLOBALS array is excluded from the normal array functionalities. For example, you can assign $GLOBALS to an array so that this array points to $GLOBALS. Therefore, the new array can modify existing variables, as shown in the following code:

$a = 1;
$globals = $GLOBALS; // Ostensibly by-value copy
$globals['a'] = 2;
var_dump($a); 

In the future, this should be prevented, so this code will throw an Error Exception. The $GLOBALS array should behave like a normal array.

Explicit Octal Integer Notation

A lot has happened in PHP when it comes to comparing strings and integers. Comparisons have become more explicit since PHP 8, which led to new problems. There are numbers that contain letters, for example a hexadecimal or binary number. So with $x = 0b1101; the binary value can be assigned to a variable. With $y = 0xBF12; for octal values there is the possibility to simply work with a 0 at the beginning: $z = 016;. However, there are cases where you can also get an octal value from an integer by mistake or by type casting. So in PHP 8 there is a 0o or 0O prefix used to explicitly define a value as octal:

$octal = 0o16 == 14; // comes out as true here

MySQLi default error mode

Take caution here: This new feature could cause problems in existing code. Since PHP 8, the PDO Error Mode has been changed to Exception and all error messages coming from a database are output directly - unless you catch them first. Now, the same behavior would like to be transferred to MySQLi. Currently, it’s exactly the same there as with the old PDO: errors aren’t caught and the code will continue to be executed. As of PHP 8, the mysqli_report should be set to MYSQLI_REPORT_ERROR and MYSQLI_REPORT_STRICT [9]. As of PHP 8, an exception is thrown here, so existing sections of code that relied on silent mode can now throw errors. If you still want to suppress the errors on your system, then you have to use the function mysqli_report with the parameter MYSQLI_REPORT_OFF.

New fsync() function

The fflush() function has been around for a while. It forces the entire output buffer to be written to the file pointer that was passed as a parameter. However, this function only receives a true or false from the operating system running PHP. The operating system itself must transfer content to the file system. This is where problems can arise that PHP itself is not even aware of. With the function fsync(), you should learn from the script if the output buffer was also written persistently. Only then can you receive a true for success. Additionally, another function fdatasync() will be introduced to ensure that data (but not metadata) is stored as it is in fsync. These may not be relevant in some cases and can be ignored. In Windows, the function fdatasync should be an alias for fsync, since writing the buffer without metadata is not supported by Windows.

Enumerations

With enums, a new language construct is introduced in version 8.1. But what are enumerations? You want to provide a certain number of options in the code, but these aren’t always easy to validate or query. A typical example in a web application would be salutation or gender. You’d like to offer only certain options and permit only them. In a database, an ENUM field can already be defined. But in PHP, up until now, developers had to take care of it themselves. The difficult part is querying in the code. Each time a value is received, the entire list must be read and cross-checked.

$salutation = ['Mr.','Ms.','Not stated'];
 
function salutationIsValid($salutation,$salutation){
  return in_array($salutation,$salutation);
}

Developers are forced to always drag this logic along and validate it wherever salutation is used. With PHP 8.1, now you can define the following construct:

enum Salutation{
  case Mr;
  case Ms;
  case NA;
}

With this construct, we can now define type hints:

public function setSalutation(Salutation $salutation)

You can now pass Salutation::Mr. or Salutation::Ms. as parameters to the method. It’s important to keep in mind that each case in an enum is its own internal instance and so, the cases cannot be compared with each other.

$a = Salutation::Mr;
$b = Salutation::Ms;
$a === $b; // false

The enum can be checked with an instanceof. If you want a value of the enum, then you have to use the name property to get the string.

echo Salutation::Mr name; // "Mr." is output

Now comes the question of how this would look applied in a project. There are enums that have a different value in a database than the identifier. For this, a value can be assigned to a case using the following construct:

enum Salutation{
  case Mr = '1';
  case Ms = '2';
  case NA = '0';
}

This construction is also called a Backed Enum, and one without values is a Pure Enum. The values of cases may only have a scalar type - an integer or string - and may not have union types, etc. If a case is assigned a value, then all other cases must also be assigned a value, as they are not automatically filled with values. For accessing the case value, there is the value property:

echo Salutation::Mr.→value; // outputs "1"

Since value is a read-only property, no references may be formed from it. &Salutation::Mr.→value; would output an error message. The backed Enums implement a BackedEnumInterface and have two additional methods:

from(int|string): self This allows a case to be read from the enum using a string, for example, to convert a value from the database to an enum:

$row = $statement fetch();
$salutation = Salutation::from($row['salutation']);

If the value is not a part of the enum, then a ValueError error message is issued. But if you want to have a nullable value, this method can be used:

tryFrom(int|string): ?self

The tryFrom method works exactly like the from method, but it does not immediately return an error message, it just returns a null. Both methods expect an integer or string as a parameter, but if the strict_type has not been declared in the file, then other scalar types can be passed, such as float. So you could use the tryFrom method for a fallback value:

$row = $statement->fetch();
$salutation = Salutation::tryFrom($row['salutation'])?? Salutation::NA;

An enum can contain methods and implement interfaces. The syntax can be seen in Listing 2.

Listing 2

interface SpecialInterface {
  public function isSpecial(): bool;
}
enum OrderStatus implements SpecialInterface {
  case ARCHIVED = 1;
  case CANCELED = 2;
  case ORDERED = 3;
  case PAYED = 4;
  case DELIVERED = 5;
 
  public function isSpecial(){
  return match($this) {
    OrderStatus::ARCHIVED, OrderStatus::CANCELED => true,
    OrderStatus::ORDERED, OrderStatus:: PAYED, OrderStatus::DELIVERED => false,
    }
  }
}
$currentStatus = OrderStatus::from(5); // delivered is read
var_dump($currenStatus->isSpecial()); // false is output

Within a method there is the variable $this. It’s available for every single case. Of course, Private and Protected methods are also allowed, but the latter two are equal since Enums cannot be derived. Additionally, static methods are also allowed. You no longer have $this but static:: is available.

enum Size {
  case Small;
  case Medium;
  case Large;
 
  public const Huge = self::Large;
}

However, there are some problems in this kind of assignment that you should be aware of. For instance, an enum can implement an Array Access interface. Individual cases can be an array, but then you can’t assign the enum to a constant.

Listing 3

// This is an entirely legal Enum definition.
enum Direction implements ArrayAccess {
  case Up;
  case Down;
 
  public function offsetGet($val) { ... }
  public function offsetExists($val) { ... }
  public function offsetSet($val) { throw new Exception(); }
  public functiond offsetUnset($val) { throw new Exception(); }
}
 
class Foo {
  // This is allowed.
  const Bar = Direction::Down;
 
  // This is disallowed, as it may not be deterministic.
  const Bar = Direction::Up['short'];
  // Fatal error: Cannot use [] on enums in constant expression
}
 
// This is entirely legal, because it’s not a constant expression.
$x = Direction::Up['short'];

As seen in Listing 3, it’s possible to implement an ArrayAccess interface. However, this must not be assigned to a constant if an array key is used. The entire case, however, can be assigned very well. There are few differences between an enum and a normal object. Enums have no state and cannot be derived, so you can think of an enum as a kind of Final Static Class. Since an enum has no state, you can’t initialize an enum with a new keyword, and you can’t create a new instance using a ReflectionClass.

Enums implement a UnitEnum interface that automatically brings the cases() method with it. It can be used to output all options that an enum contains as an array.

Enum Cases cannot be used as array keys, but you can use them as keys in a SplObjectStorage or a WeakMap. Since an enum is a singleton and is never deleted by the garbage collector, it makes no difference whether SplObjectStorage or WeakMap is used. Of course, enums are accompanied by the appropriate reflection classes, so you can also display an enum in detail. The ReflectionEnum class will be in PHP 8.1, which lets you read the cases and determine if an enum is a Backed Enum or a Pure Enum. There will also be a reflection class for each case: ReflectionEnumUnitCase. For Backed Enum Cases, ReflectionEnumBackedCase will be introduced in order to read the assigned value of the case with getBackingValue(). Now, let’s look at an example in Listing 4 that shows how you can can use an enum.

Listing 4

enum QueueJobStatus: int{
  case STOPPED = -1;
  case WAITING = 0;
  case RUNNING = 1;
  case DONE = 2;
  case FAILED = 3;
 
public function label(): string{
  return match($this) {
  static::ONHOLD =>'stopped',
  static::WAITING => 'waiting',
  static::RUNNING => 'running',
  static::DONE => 'successful',
  static::FAILED => 'failed'
  }
 }
}
 
$dbStatus = QueueJobStatus::from($row['status']);
 
foreach(QueueJobStatus::cases() as $status){
  $selected = $status === $dbStatus?' selected':'';
  echo sprintf('<option vaue="%s"%s>%s</option>',$case->value,$selected,$case->label());
}

Conclusion

At the time of writing this article, we don’t know yet if generics will also be implemented in PHP 8.1 or if we’ll only see them in later versions. But what we can say for sure is that enums look very promising and will extend the PHP programming language as an important and useful new tool.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK