PHP 8.1: What's New and Changed
PHP 8.1 is currently in active development, and it will be released towards the end of year 2021.
New Features
- Enums
- Curl: File uploads from strings with `CURLStringFile`
- Array unpacking support for string-keyed arrays
- Explicit Octal numeral notation
- New `array_is_list` function
- Hash functions accept algorithm-specific `$options`
- xxHash hash algorithms support
- MurmurHash3 hash algorithm support
- Curl: DNS-over-HTTPS support
Syntax/Functionality Changes
- HTML entity en/decode functions process single quotes and substitute by default
- GD Extension: Font identifiers are `\GdFont` class objects
- FTP Extension: Connection resources are `\FTPConnection` class objects
- IMAP: `imap` resources are `\IMAPConnection` class objects
- finfo Extension: `file_info` resource are migrated to existing `finfo` objects
- Pspell Extension: `pspell`, `pspell config` resources are `\PSpell`, `\PSpellConfig` class objects
- `version_compare` operator restrictions
Deprecations
Enums
PHP 8.1 adds support for Enumerations. An Enumeration, or an Enum for short, is an enumerated type that has a fixed number of possible values.
RFC in progress.
This RFC is still being ironed out with some implementation details. This post will be actively updated, and this noticed removed once the Enum feature is merged to PHP.
A popular analogy for an Enum is suits in a deck of playing cards. A deck of playing cards has four suits, and they are fixed: Clubs, Diamonds, Hearts, and Spades.
In PHP, these suits can be enumerated with an Enum:
enum Suit {
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
With Suit
Enum, it is now possible to enforce types when accepting or returning a suit value:
function pick_card(Suit $suit) {}
pick_card(Suit::Clubs);
pick_card(Suit::Diamonds);
pick_card(Suit::Hearts);
pick_card(Suit::Spades);
In contrast to using special strings or numbers internally (i.e. magic numbers) to store and work with parameters, Enums make the application code more readability, and avoids unexpected application state.
Enum Syntax
PHP 8.1 reserves and uses enum
keyword to declare Enums. The syntax is similar to a trait/class/interface syntax:
enum Suit {
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
Enums are declared with the enum
keyword, followed by the name of the Enum. An Enum can optionally declare string
or int
as backed values. Enums can also extend a class and/or implement interfaces.
Internally at the PHP parser level, there is a new token with named T_ENUM
with value 369
assigned.
Enums can also hold a value for each case, which makes them Backed Enums.
enum HTTPMethods: string {
case GET = 'get';
case POST = 'post';
}
Following is an example of an Enum that declares backed value type, and implements an interface.
enum RolesClassLikeNamespacedEnum: string implements TestFor {
case Admin = 'Administrator';
case Guest = 'Guest';
case Moderator = 'Moderator';
}
New enum_exists
function
PHP 8.1 also adds a new enum_exists
function to check if a given Enum exists.
function enum_exists(string $enum, bool $autoload = true): bool {}
Note that due to class-semantics of Enums, class_exists
function also returns true
for an Enum.
UnitEnum
interface
Enums that are not backed with a value automatically implement UnitEnum
interface.
interface UnitEnum {
public static function cases(): array;
}
Enums cannot explicitly implement this interface as it is internally done by the engine. This is only to assist in determining the type of a given Enum. The UnitEnum::cases
method returns an array of all cases of a given Enum.
PHP classes are not allowed to implement this interface, and results in an error if attempted:
class FakeEnum implements UnitEnum {}
Fatal error: Non-enum class FakeEnum cannot implement interface UnitEnum in ... on line ...
Although Enums allow methods declared on them, declaring a method with name cases
is not allowed:
enum Foo {
public function cases(): array{}
}
Fatal error: Cannot redeclare Foo::cases() in ... on line ...
BackedEnum
interface
If an Enum declares scalar backed values, that Enum automatically an interface called BackedEnum
. Similar to UnitEnum
interface, it is not possible to explicitly implement BackedEnum
interface.
interface BackedEnum extends UnitEnum {
public static function from(int|string $value): static;
public static function tryFrom(int|string $value): ?static;
}
See BackedEnum::from
and BackedEnum::tryFrom
for their usage information.
Standard classes are not allowed to implement this interface.
class FakeEnum implements BackedEnum {}
Fatal error: Non-enum class FakeEnum cannot implement interface BackedEnum in ... on line ...
Similar to the restriction on not allowing a method with name cases
is not allowed, any backed Enum must not declare a from
or tryFrom
method:
enum Foo: int {
public function from() {}
public function tryFrom() {}
}
Fatal error: Cannot redeclare Foo::from() in ... on line ...
Fatal error: Cannot redeclare Foo::tryFrom() in ... on line ...
Declaring Enums
Enums are internally implemented on top PHP classes, and they inherit most of the class semantics with additional restrictions imposed.
Enums support namespaces, autoloading, they can have methods (but not properties), implementing interfaces, and many other behaviors associated with PHP classes.
A basic enum is simply an enum
structure, where each case is declared with the case
keyword. With Enums supported in PHP 8.1, PHP now reserves "enum"
as a reserved word, and prevents any functions, classes, interfaces, etc. from being created with enum
. It can be part of a namespace due to changes in how PHP considers reserved keywords in namespaced values.
Enums can have zero or more cases
Within an enum
structure, it may contain any number of "cases", from zero to unlimited. Both of these Enum declarations are valid:
enum ErrorStates {
}
enum HTTPMethods {
case GET;
case POST;
}
Enums may have optional values
It is possible to assign a string
or int
value to each case in an Enum. This can be useful when serializing data, storing them in a database, etc.
Enums that hold a value, i.e. a backed Enum, must:
- Declare the scalar type in the Enum declaration. Only
string
orint
is allowed. - Assign values for all cases.
- Hold values of same scalar type. It is not allowed to store
string
andint
values mixed. - Cases and assigned values are unique
enum Suit: string {
case Clubs = '♣';
case Diamonds = '♦';
case Hearts = '♥';
case Spades = '♠';
}
Backed Enums must declare the scalar type
In order for an Enum to be to associate values for each case (i.e. a Backed Enum), it must declare the scalar type at the Enum declaration.
Not doing so results in an error:
enum HTTPMethods {
case GET = 'get';
case POST = 'post';
}
Fatal error: Case GET of non-scalar enum HTTPMethods must not have a value in ... on line ...
Enums only support string
and int
scalar types. Any other type, including bool
, null
, ?string
, ?int
, or even string|int
union types are not allowed.
enum HTTPMethods: object {
case GET = 'get';
case POST = 'post';
}
Fatal error: Enum scalar type must be int or string, object given in ... on line ...
Backed Enums must assign values for all cases
This section is work-in-progress
If an Enum declares the scalar type of values, it must set a value for all cases:
enum HTTPMethods: string {
case GET;
case POST;
}
In the snippet above, HTTPMethods
Enum is declared to contain string
, but the cases are not assigned a value. This is not allowed, and results in an error.
Backed Enums must hold values of same scalar type
With a Backed Enum, the values assigned to each case must be of the same type as declared in the type.
PHP strictly enforces this even with strict_types not enabled.
enum HTTPMethods: int {
case GET = 1;
case POST = '2';
}
Fatal error: Enum case type string does not match enum scalar type int in ... on line ...
Backed Enum cases and values must be unique
This section is work-in-progress
A valid Enum must not contain duplicate cases, or duplicate values. For example, both of these declarations are invalid:
enum Test {
case FOO;
case FOO;
}
enum Test: string {
case FOO = 'baz';
case BAR = 'baz';
}
Enum values must be unique as well, because
BackedEnum::from
supports retrieving an Enum object from a given scalar value.
Case-Sensitivity
The name of the Enum itself is case-insensitive, and it follows how PHP treats classes and functions in a case-insensitive manner.
Individual cases in an Enum are case-insensitive.
enum CaseInSenSitive {
case bAR;
case Bar;
}
Class Semantics in Enums
Namespaces
Enums support namespaces. They follow the standard namespace syntax that is otherwise used in classes, traits, interfaces, functions, etc.
namespace Foo\Bar;
enum HTTPMethods {}
Autoloading
Just like PHP supports autoloading for classes, traits, and interfaces, Enums support autoloading as well.
Note that this might require updates to class-map generators that scan files for autoloadable items.
Magic constants
Enums fully support all magic constants that PHP supports for classes.
::class
constant that refers to the name of the Enum itself.__CLASS__
magic constant that refers to the name of the Enum from within the Enum.__FUNCTION__
in Enum method context.__METHOD__
in Enum method context.
Class/object functions and instanceof
Enums behave similar to classes when they are used with functions that support inspecting classes and objects.
For example, gettype
, is_a
, is_object
, get_class
and get_debug_type
(new in PHP 8.0) functions behave as if an Enum is a standard PHP object.
enum Suit {
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
gettype(Suit::Clubs); // "object"
is_object(Suit::Spades); // true
is_a(Suit::Clubs, Suit::class); // true
get_class(Suit::Clubs); // "Suit"
get_debug_type(Suit::Clubs); // "Suit"
Suit::Clubs instanceof Suit; // true
Suit::Clubs instanceof UnitEnum; // true
Suit::Clubs instanceof object; // false
Enums with Properties, Methods, and Traits
Enums are made in a way that it can compare one Enum case with another. Enums must be stateless, as in they do not allow storing properties in them.
Enums allow methods
Enums can contain methods. They also support standard method visibility modifiers as well as static methods.
This can be quite useful use cases such as declaring a label(): string
method that returns a user-friendly label.
enum HTTPStatus: int {
case OK = 200;
case ACCESS_DENIED = 403;
case NOT_FOUND = 404;
public function label(): string {
return static::getLabel($this);
}
public static function getLabel(self $value): string {
return match ($value) {
HTTPStatus::OK => 'OK',
HTTPStatus::ACCESS_DENIED => 'Access Denied',
HTTPStatus::NOT_FOUND => 'Page Not Found',
};
}
}
echo HTTPStatus::ACCESS_DENIED->label(); // "Access Denied"
echo HTTPStatus::getLabel(HTTPStatus::ACCESS_DENIED); // "Access Denied"
The snippet above uses
match
expressions added in PHP 8.0
Enums can implement interfaces
Enums can implement interfaces. Enums must fulfill the contracts of the interfaces just like standard class must.
interface Foo {}
enum FooEnum implements Foo {}
Enums must not contain properties
One of the most important differences between an Enum and a class is that Enums are not allowed to have any state. Declaring properties, or setting properties is not allowed. static properties are not allowed either.
enum Foo {
private string $test;
private static string $test2;
}
Fatal error: Enums may not include member variables in ... on line ...
Further, dynamically setting properties is not allowed either:
enum Foo {
case Bar;
}
$bar = Foo::Bar;
$bar->test = 42;
Error: Enum properties are immutable in ...:...
Instantiating with new
is not allowed
Although Enum cases themselves are objects, it is not allowed to instantiate them with the new
construct.
Both of the following new
constructs are not allowed:
enum Foo {
case Bar;
}
new Foo(); // Fatal error: Uncaught Error: Cannot instantiate enum Foo
new Foo::Bar(); Parse error: syntax error, unexpected identifier "Bar", expecting variable or "$"
Enums cannot be extended, and must not inherit
This section is work-in-progress
Enums are internally declared as final, and Enum may not inherit from another Enum or a class.
enum Foo extends Bar {}
Parse error: syntax error, unexpected token "extends", expecting "{" in ... on line ...
If a class attempts to extend an Enum, that will result in an error as well because all Enums are declared final.
enum Foo {}
class Bar extends Foo {}
Fatal error: Class Bar may not inherit from final class (Foo) in ... on line ...
Enums support property-less traits
Enums can use
traits, as long as the trait does not declare any properties.
trait NamedDocumentStatus {
public function getStatusName(): string {}
}
enum DocumentStats {
use NamedDocumentStatus;
case DRAFT;
case PUBLISHED;
}
If the traits used contains any properties (static or otherwise), using that trait results in a fatal error:
trait NotGood {
public string $value;
}
enum Foo {
use NotGood;
}
Fatal error: Enum "Foo" may not include properties in ... on line ...
Disallowed magic methods
To prevent Enum objects from having any state, and to ensure two Enums are comparable, Enums disallow implementing several magic methods:
__get()
: To prevent maintaining state in Enum objects.__set()
: To prevent dynamic property assignment and maintaining state.__construct()
: Enums do not supportnew Foo()
construct all.__destruct()
: Enums must not maintain state.__clone()
: Enums are uncloneable objects.__sleep()
: Enums do not support life-cycle methods.__wakeup()
: Enums do not support life-cycle methods.__set_state()
: To prevent coercing state to Enum objects.
All of the following magic method declarations are not allowed.
enum Foo {
public function __get() {}
public function __set() {}
public function __construct() {}
public function __destruct() {}
public function __clone() {}
public function __sleep() {}
public function __wakeup() {}
public function __set_state() {}
}
If they are declared, it will trigger a fatal error with a message similar to this:
Fatal error: Enum may not include __get in ... on line ...
Classes vs Enums
Enums store fixed values, with optional backed values, and they do not allow state. They serve distinctly different purposes, but Enums share some semantics with classes.
Classes | Enums | |
---|---|---|
Syntax | class Foo {} |
enum Foo {} |
Properties | ✔ | ✘ |
Static Properties | ✔ | ✘ |
Methods | ✔ | ✔ |
Static Methods | ✔ | ✔ |
Autoloading | ✔ | ✔ |
Instantiating: new Foo() |
✔ | ✘ |
Implement interfaces | ✔ | ✔ |
Inheritance: Foo extends Bar |
✔ | ✘ |
Magic Constants: ::class , __CLASS__ , etc. |
✔ | ✔ |
Object Comparison Foo === Foo |
Not equal | Equals |
Traits | Supports | Supports without properties |
Using Enums
The main use case of Enums is type safety. The PHP engine will ensure that a passed or returned value is belongs to one of the allowed values. Without having to validate passed values belongs to one of the allowed types, the PHP engine enforces it, and allows IDEs and static analyzers to highlight potential bugs as well.
With Backed Enums, it is possible to store the enumerated values in a database or other storage, and restore a programmatically identical object.
Comparing Enum Values
At any given time, two Enums holding the same value is considered identical, just as how PHP considers two strings to be identical. This is a major difference between two objects, because two objects from an instantiated class are not considered identical even though they hold exact same values.
new stdClass() === new stdClass(); // false
Suit::Hearts === Suit::Hearts; // true
Enums as Parameter, Property and Return Types
Enums can be used as parameter, return, and property types. When the Enum name is used, the passed/set/return values must be one of the Enumerated values.
enum Suit {
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
function play(Suit $suit, string $value) {}
The play
function accepts an enumerated value of Suit
, and passing any other value will result in a \TypeError
. This can greatly improve the code because PHP validates the passed values without having to write additional validations inside the play
function.
play(Suit::Clubs, 'J');
play(Suit::Hearts, 'Q');
If any value other than a Suit
Enum value is passed, it will cause a type error:
play('clubs', 'J');
TypeError: play(): Argument #1 ($suit) must be of type Suit, string given
It does not allow to use values from another Enum either:
play(UnoColors::Blue, 'J');
TypeError: play(): Argument #1 ($suit) must be of type Suit, UnoColors given
Further, if an undefined Enum value is used, it will cause an undefined class constant because Enum values and class constants share the same syntax.
Enum name
and value
properties
Each Enum contains a name
property that refers to the name of the property. This value is read-only.
enum Suit {
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
echo Suit::Clubs->name; // "Clubs"
In a Backed Enum, there is also value
property for the backed value (either string
or int
).
enum Suit: string {
case Clubs = '♣';
case Diamonds = '♦';
case Hearts = '♥';
case Spades = '♠';
}
echo Suit::Clubs->name; // "Clubs"
echo Suit::Clubs->value; // "♣"
Attempting to change name
or value
properties will result in an error:
Suit::Clubs->name = 'Hearts';
Error: Enum properties are immutable in ...:...
Further, Enums without a backed value (UnitEnum
) do not have a value
property. Only Backed Enums (BackedEnum
) have value
property. Attempting to use an undefined property (including value
) currently raises a warning in PHP 8.1.
enum Suit {
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
echo Suit::Clubs->value;
Warning: Undefined property: Suit::$value in ... on line ...
Getting All Enum Values
Both UnitEnum
and BackedEnum
Enums support a ::cases
method, that returns all values of the Enum.
enum Suit {
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
Suit::cases();
// [Suit::Clubs, Suit::Diamonds, Suit::Hearts, Suit::Spaces]
The Suit::cases()
method returns an array of all possible Enums. The return values are Enum objects themselves, and not the name or value properties.
Getting Enum by a backed value
All Backed Enums (BackedEnum
) support from
and tryFrom
methods that allow retrieving an instance from the backed value.
enum Suit: string {
case Clubs = '♣';
case Diamonds = '♦';
case Hearts = '♥';
case Spades = '♠';
}
$clubs = Suit::from('♥');
var_dump($clubs); // enum(Suit::Hearts)
echo $clubs->name; // "Hearts";
echo $clubs->value; // "♥"
If there is no Enum by that value, PHP will throw a ValueError
exception.
There is also a tryFrom
method, that returns null
if an Enum does not exist by that value.
enum Suit: string {
case Clubs = '♣';
case Diamonds = '♦';
case Hearts = '♥';
case Spades = '♠';
}
$clubs = Suit::tryFrom('not-existing');
var_dump($clubs); // null
Backwards Compatibility Impact
Enums use a new syntax, and it is not possible to use Enums in PHP versions prior to 8.1.
Native PHP approaches such as myclabs/php-enum are not compatible with PHP 8.1 Enums.
The Enum syntax will result in a ParserError
in PHP < 8.1.
Curl: File uploads from strings with `CURLStringFile`
PHP Curl extension supports making HTTP(s) requests with file uploads. Since PHP 5.5, PHP Curl extension provides a CURLFile
class that accepts a URI or a path to a file, a mime type, and a file name to upload it as.
With CURLFile
class, it accepts a path or a URI, and not the contents itself. For files already stored on disk or any other stream, using CURLFile
class is quite straight-forward:
$txt_curlfile = new \CURLFile('test.txt', 'text/plain', 'test.txt');
$ch = curl_init('http://example.com/upload.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, ['file' => $txt_curlfile]);
curl_exec($ch);
Using CURLFile
was difficult if the file being uploaded is already stored in the memory, or not written to disk. It was possible to use data://
URIs with Base64 encoding, but it was not straight forward.
$txt = 'test content';
$txt_file = 'data://application/octet-stream;base64,' . base64_encode($txt);
$txt_curlfile = new \CURLFile($txt_file, 'text/plain', 'test.txt');
$ch = curl_init('http://example.com/upload.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, ['file' => $txt_curlfile]);
curl_exec($ch);
From PHP 8.1 and later, Curl extension has a new CURLStringFile
class, that works similar to CURLFile
class, but accepts the contents of the file instead of a path or a URI.
$txt = 'test content';
$txt_curlfile = new \CURLStringFile($txt, 'text/plain', 'test.txt');
$ch = curl_init('http://example.com/upload.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, ['file' => $txt_curlfile]);
curl_exec($ch);
The new CURLStringFile
class makes it easy to make a Curl file upload request using data that is already stored in memory. Some example use cases include uploading an image that PHP processes and is stored in a PHP variable, a processed XML file, or a PDF file generated from a PHP library.
CURLStringFile
class synopsis
class CURLStringFile {
public string $data;
public string $postname;
public string $mime;
public function __construct(string $data, string $postname, string $mime = "application/octet-stream") {}
}
- Unlike
CURLFile
class,CURLStringFile
class objects allow serializing and unserializing. CURLStringFile
objects can be clone. This is true forCURLFile
instances as well.CURLStringFile
class enforces typed properties. It is not allowed to set values other thanstring
values.
CURLStringFile
Polyfill
Curl extension treats CURLFile
class objects as a file upload, and it treats the same way for all classes that extend CURLFile
.
Taking advantage of that, it is possible to create a polyfill for CURLStringFile
.
class CURLStringFile extends CURLFile {
public function __construct(string $data, string $postname, string $mime = "application/octet-stream") {
$this->name = 'data://'. $mime .';base64,' . base64_encode($data);
$this->mime = $mime;
$this->postname = $postname;
}
}
Internally, the polyfilled CURLStringFile
class behaves exactly same as a CURLFile
class with a data://
URI, but it is not handled inside CURLStringFile::__construct
hiding the complexity, and working as a polyfill.
The polyfill above should work in all PHP versions >= 7.0.
Backwards Compatibility Impact
CURLStringFile
is a new class from the Curl extension, and it will not be possible to use it in PHP versions older than PHP 8.1. However, it is possible to polyfill this class to older PHP versions.
Array unpacking support for string-keyed arrays
Since PHP 7.4, PHP supports array spread operator ( ...
) for array unpacking. An array can be unpacked with another array if the array expression is prefixed with the spread operator. This effectively results in an array_merge
of all arrays in the expression.
$array_1 = ['foo', 'bar'];
$array_2 = ['baz', 'qux'];
$array_unpacked = [...$array_1, ...$array_2, ...['quux']];
$array_merged = array_merge($array_1, $array_2, ['quux']);
var_dump($array_unpacked);
// ['foo', 'bar', 'baz', 'qux', 'quux'];
var_dump($array_merged);
// ['foo', 'bar', 'baz', 'qux', 'quux'];
array_merge
function merges all gives arrays in the order they are passed. Arrays with numeric keys will appended and renumbered, and duplicate string keys will be overwritten with later values.
Prior to PHP 8.1, the spread operator only supported arrays with numeric keys. Using an array with string key resulted in an error prior to PHP 8.1:
$array = [...['a' => 'foo'], ...['b' => 'bar']];
Fatal error: Cannot unpack array with string keys in ... on line ...
From PHP 8.1, array unpacking supports arrays with string keys as well.
$array = [...['a' => 'foo'], ...['b' => 'bar']];
// ['a' => 'foo', 'b' => 'bar'];
Array Unpacking vs array_merge
Array unpacking and array_merge
results in identical values.
$array_unpack = [
...['a' => 'foo'],
...['b' => 'bar'],
...['c' => 'baz']
];
$array_merge = array_merge(
['a' => 'foo'],
['b' => 'bar'],
['c' => 'baz'],
);
$array_unpack === $array_merge; // true
Array Unpacking vs +
operator
Array unpacking overwrites duplicate string keys, while the +
operator ignores duplicate string keys.
var_dump(
['a' => 'foo']
+['a' => 'bar']
);
// ['a' => 'foo'];
var_dump([
...['a' => 'foo'],
...['a' => 'bar']
]);
// ['a' => 'bar'];
Backwards Compatibility Impact
The array unpacking operator did not support string-keys prior to PHP 8.1, and resulted in a fatal error. Existing code that runs successfully on older versions should not cause any issues in PHP 8.1 or later.
array_merge
results in identical results in all PHP versions. array_merge
, and should be used on code that requires compatibility with PHP versions prior to 8.1.
Explicit Octal numeral notation
PHP supports various numeral systems, including the default decimal (base-10), binary (base-2), octal (base-8), and hex (base-16).
Numeral systems apart from decimal are prefixed with their own prefix:
- Hex with
0x
prefix: e.g.0x11
= 17 - Binary with
0b
prefix: e.g.0b11
= 3 - Octal with
0
prefix: e.g.011
= 9
In PHP 8.1 and later, Octal numerals also support the 0o
(zero, followed by o as in Oscar) prefix, which means octal numeric literals can be made more obvious and readable that they are indeed octal numerals. 0O
(zero, followed by upper case O as in Oscar) is also supported.
Many programming languages use
0
prefix for octal numeric literals, just like PHP. However, many languages also support the explicit0o
notation and have been recommending the0o
.
echo 0xff; // 255
echo 0b111; // 7
echo 77; // 77
echo 077; // 63
echo 0o77; // 63, In PHP 8.1
echo 0O77; // 63, In PHP 8.1
077 === 63; // true
077 === 0o77; // true
077 === 0O77; // true
Further, the underscore numeric separators are supported in 0o
/0O
prefixed numeric literals as well:
echo 0o7716_1412; // 16573194
0
Prefix Is Not Changed
The existing 0
prefix is not deprecated, and continue function exactly the same.
The 0o
/0O
prefixes are supported in addition to the existing 0
prefix.
No Changes in Numeric Strings
Numeric strings, i.e. strings that contain a number, are not impacted by this change. "0o16"
(as a string) will not be interpreted as a number, and does not return true
for is_numeric
function either.
echo "016"; // "016"
is_numeric("0o77"); // false
is_numeric("0b11"); // false
is_numeric("0x16"); // false
Note that "016"
returns true
for is_numeric
, and it is not changed:
is_numeric("016"); // true
Backwards Compatibility Impact
The 0o
and 0O
prefixes are new prefixes, that were not supported in older PHP versions. Attempting to use them in older PHP versions result in a syntax error:
$value = 0o77;
Parse error: syntax error, unexpected identifier "o77" in ... on line ...
New `array_is_list` function
PHP 8.1 brings a new function array_is_list
, that returns whether a given array is an array with all sequential integers starting from 0.
In other words, this function returns true
if the given array is semantic list of values; an array with all keys are integers, keys start from 0, with no gaps in between.
array_is_list
function returns true
on empty arrays as well.
array_is_list([]); // true
array_is_list([1, 2, 3]); // true
array_is_list(['apple', 2, 3]); // true
array_is_list(['apple', 'orange']); // true
array_is_list([0 => 'apple', 'orange']); // true
array_is_list([0 => 'apple', 1 => 'orange']); // true
Any array with keys not starting from zero, or any array with not all keys are integers in sequential order evaluates to false
:
// Key does not start with 0
array_is_list([1 => 'apple', 'orange']); // false
// Keys are not in order
array_is_list([1 => 'apple', 0 => 'orange']); // false
// Non-integer keys
array_is_list([0 => 'apple', 'foo' => 'bar']); false
// Non-sequential keys
array_is_list([0 => 'apple', 2 => 'bar']); false
array_is_list
Function Synopsis
function array_is_list(array $array): bool {
}
array_is_list
only acceptsarray
parameters. Passing any other type will throw aTypeError
exception.array_is_list
function does not acceptiterable
or other array-like class objects such asArrayAccess
,SPLFixedArray
, etc.array_is_list
is declared in the global namespace.
Polyfill
array_is_list
function can be trivially polyfilled with user-land PHP code:
function array_is_list(array $array): bool {
if (empty($array)) {
return true;
}
$current_key = 0;
foreach ($array as $key => $noop) {
if ($key !== $current_key) {
return false;
}
++$current_key;
}
return true;
}
Backwards Compatibility Impact
array_is_list
is a new function added in PHP 8.1. Unless there is an existing function with the same name in global namespace, there should be no backwards-compatibility issues.
Further, array_is_list
function can be back-ported to almost any PHP version.
Hash functions accept algorithm-specific `$options`
Hash functions in PHP accept a new optional $options
parameter in PHP 8.1, that can further specify custom algorithm-specific options.
The following functions accept the new $options
parameter as their last parameter:
Function synopses
function hash(string $algo, string $data, bool $binary = false, array $options = []): string|false
function hash_file(string $algo, string $filename, bool $binary = false, array $options = []): string|false
function hash_init(string $algo, int $flags = 0, string $key = '', array $options = []): HashContext
Supported Custom Options
MurmurHash3 and xxHash algorithms support specifying custom options with the new $options
parameter.
hash('murmur3f', 'php.watch'); // "ba0189037daec822d973d304602d44f0"
hash("murmur3f", "php.watch", options: ["seed" => 42]); // 0cafc26e49efe230cdbd109458fef893
hash("xxh3", "php.watch"); // "f406cee14d73b176"
hash("xxh3", "php.watch", options: ["seed" => 44]); // acb8c705fdcd579c
These snippet use named parameters introduced in PHP 8.0.
In PHP 8.1, the following new hash $option
values are accepted:
- MurMurHash:
seed
- xxHash:
seed
andsecret
Backwards Compatibility Impact
The new $options
parameter is a new parameter with a default value of an empty array, and will not cause any backwards-compatibility issues if the hash*
functions are called with the additional $options
parameter. PHP will silently ignore the additional parameters, but note that the output will be different across PHP versions without $options
parameter support.
As of now, the only hashing algorithms that support custom options are MurmurHash3 and xxHash algorithms added in PHP 8.1.
Related Changes
xxHash hash algorithms support
xxHash is an extremely fast hashing algorithm that is not designed for cryptographic purposes, but provides excellent randomness and dispersion of output, and uniqueness of to minimize collisions. Some xxHash variants in fact, are faster than the RAM throughput provided CPU cache is sufficient and fits.
PHP 8.1 adds support for xxHash algorithm in following variants:
xxh32
: 32-bit hash outputxxh64
: 64-bit hash outputxxh3
: 64-bit hash outputxxh128
: 128-bit hash output
hash('xxh32', 'php.watch'); // cb729dd1
hash('xxh32', 'php.watch'); // 9c823891d744a55e
hash('xxh3', 'php.watch'); // f406cee14d73b176
hash('xxh128', 'php.watch'); // 16c27099bd855aff3b3efe27980515ad
xxHash is a streaming hash, and can be used as such:
$context = hash_init('xxh3');
hash_update($context, 'php.');
hash_update($context, 'watch');
hash_final($context); // f406cee14d73b176
xxHash is not a cryptographic hashing algorithm. For password hashing, use
password_hash
and its friends. Furthermore, none of the xxHash variants are allowed inhash_hmac
function.
In PHP 8.1, xxHash is the fastest hashing algorithm supported in PHP.
Algorithm | PHP implementation speed (GB/s) |
---|---|
xxh3 |
15.19 |
xxh128 |
14.78 |
crc32c |
14.12 |
xxh64 |
13.32 |
murmur3f | 8.87 |
xxh32 |
7.47 |
sha2-256 |
0.25 |
sha1-160 |
0.70 |
md5-128 |
0.77 |
The results above are an excerpt from PHP Hash Algorithm Benchmarks.
xxHash Options
PHP 8.1 supports specifying algorithm-specific options with the new $options
parameter. Along with this, xxh*
hashing algorithms accept additional options:
Only one option is accepted at a time. For example, if a seed
option is specified, it cannot use a secret
option in the same hashing operation.
If both secret
and seed
options are passed, an Error will be thrown.
hash("xxh3", "php.watch", false, ["secret" => $secret, 'seed' => 43]);
xxh3: Only one of seed or secret is to be passed for initialization in ... on line ...
seed
option
All xxHash variants accept a seed
option through the third parameter ($options
) of the hash
function.
hash('xxh3', 'php.watch'); // "f406cee14d73b176"
hash("xxh3", "php.watch", false, ["seed" => 42]); // de9956536d1e9543
hash("xxh3", "php.watch", options: ["seed" => 42]); // de9956536d1e9543
The third example uses named parameters introduced in PHP 8.0.
secret
option
xxh3
and xxh128
variants of xxHash accept a secret
value in the third parameter ($options
array).
The required key must be larger than 135 bytes, and a key longer than 256 bytes will discarded with a warning emitted.
$secret = random_bytes(256);
hash("xxh3", "php.watch", false, ["secret" => $secret]);
// "a0dad59ce6341fb0"
It is important that the secret
value has a sufficient entropy. PHP built-in random_bytes
function is a cryptographically secure random-number generator.
xxh32
and xxh64
variants do not recognize a secret
option, and if passed, it will be silently ignored.
-
Passing a
secret
value smaller than 136 bytes causes an error:hash("xxh3", "php.watch", false, ["secret" => 'ab'])
xxh3: Secret length must be >= 136 bytes, ... bytes passed in ... code on line ...
-
Passing a
secret
value larger than 256 bytes makes PHP trim the value to 256 bytes with a warning:hash("xxh3", "php.watch", false, ["secret" => str_repeat('a', 257)]);
hash(): xxh3: Secret content exceeding 256 bytes discarded in ... code on line ...
Backwards Compatibility Impact
xxHash is newly added to PHP 8.1, and older PHP versions will not be able to use xxHash algorithms via hash
method.
Attempting to use xxHash algorithms in hash
function results in a ValueError
exception:
Fatal error: Uncaught ValueError: hash(): Argument #1 ($algo) must be a valid hashing algorithm in ...:...
On PHP versions prior to 8.0, a warning will be raised:
Warning: hash(): Unknown hashing algorithm: xxh3 in ... on line ...
Alternatives implementations include:
- Megasaxon/php-xxhash - A PHP extension to add
xxhash32
andxxhash64
for xxHash variants. - exussum12/xxhash - A pure-PHP implementation with FFI support. The pure-PHP implementation is ~600x slower than the native code, and the FFI implementation is only ~0.4x slower.
Related Changes
- PHP 8.1: Hash functions accept algorithm-specific
$options
- PHP 8.1: MurmurHash3 hash algorithm support
MurmurHash3 hash algorithm support
PHP 8.1 adds support for MurmurHash hashing algorithm.
MurmurHash is a non-cryptographic hashing algorithm, and PHP 8.1 supports MurmurHash3
version of it. Following variants are supported:
murmur3a
: 32-bit hashmurmur3c
: 128-bit hash on x86 architecturemurmur3f
: 128-bit hash on x64 architecture
hash('murmur3a', 'php.watch'); // "ac96fab7"
hash('murmur3c', 'php.watch'); // "6d0fe9c3f960dc75cf42632f3e78ffda"
hash('murmur3f', 'php.watch'); // "ba0189037daec822d973d304602d44f0"
MurmurHash is a streaming hash, which means the values can be updated in sequence without having to hash the input string as a whole.
$context = hash_init('murmur3f');
hash_update($context, 'php');
hash_update($context, '.');
hash_update($context, 'watch');
$hash = hash_final($context); // "ba0189037daec822d973d304602d44f0"
MurmurHash is not designed as a cryptographic hashing algorithm. For password hashing, use
password_hash
and its friends. Furthermore, none of the MurmurHash variants are allowed inhash_hmac
function.
MurmurHash algorithm is faster than most of the hashing algorithms, and is one of the fastest non-cryptographic algorithms available in PHP, along with the new xxHash
algorithms.
Algorithm | PHP implementation speed (GB/s) |
---|---|
murmur3a |
3.98 |
murmur3c |
6.20 |
murmur3f |
8.87 |
sha2-256 |
0.25 |
sha1-160 |
0.70 |
md5-128 |
0.77 |
The results above are an excerpt from PHP Hash Algorithm Benchmarks.
Custom Seed Options
PHP 8.1 supports specifying algorithm-specific options with the new $options
parameter. Along with this, murmur*
hashing algorithms accept additional options. The only supported option is seed
value.
hash('murmur3f', 'php.watch'); // "ba0189037daec822d973d304602d44f0"
hash("murmur3f", "php.watch", false, ["seed" => 42]); // 0cafc26e49efe230cdbd109458fef893
hash("murmur3f", "php.watch", options: ["seed" => 42]); // 0cafc26e49efe230cdbd109458fef893
The third example uses named parameters introduced in PHP 8.0.
Backwards Compatibility Impact
MurmurHash is newly added to PHP 8.1, and due to current lack of a hash registry that supports adding custom hashing algorithms, producing MurmurHash hashes using the hash()
function is not possible on older PHP versions.
On PHP 8.0, a ValueError
exception will be thrown if attempted to hash('murmur...', '...')
:
Fatal error: Uncaught ValueError: hash(): Argument #1 ($algo) must be a valid hashing algorithm in ...:...
On PHP versions prior to 8.0, a warning will be raised:
Warning: hash(): Unknown hashing algorithm: murmur3a in ... on line ...
Alternatives implementations include:
- rryqszq4/php-murmurhash - A PHP extension that adds MurmurHash support.
- lastguest/murmurhash-php - A user-land PHP implementation of MurmurHash.
Related Changes
Curl: DNS-over-HTTPS support
In PHP 8.1, the Curl extension supports specifying a server for DNS-Over-HTTPS. It requires PHP to be compiled with libcurl version 7.62 or later.
Most of the current operating systems and distributions already support it, as they often include Curl 7.68 or later in Ubuntu 20.04, and other Linux/Windows/Mac OS operating systems.
A list of public DoH servers are available at Curl documentation.
CURLOPT_DOH_URL
The DNS-Over-HTTPS server URL is configured by setting the CURLOPT_DOH_URL
option. This constant will only be available if the underlying libcurl version is >= 7.62.
$ch = curl_init('https://example.com');
curl_setopt($ch, CURLOPT_DOH_URL, 'https://dns.google/dns-query');
curl_exec($ch);
The behavior of the CURLOPT_DOH_URL
is exactly same as the libcurl behavior; see CURLOPT_DOH_URL
explained.
DoH Server URL Validation
The entered server URL is not validated at the time it is set. It is validated when the Curl request is executed. It must be an HTTPS URL.
If the provided DNS server is not a valid URL, or does not return a valid response, the request will fail. There is no fall-back to system DNS resolver, or a default DoH server configured in Curl.
Unset DoH Server URL
To unset a previously configured DoH server URL, set its value to null
.
curl_setopt($ch, CURLOPT_DOH_URL, null);
Note that the Curl handle's DNS cache is independent of DoH servers. Setting a different DoH server URL, or unsetting it will not clear the DNS cache, and Curl will reuse values previously returned by system DNS resolver or any of the DoH servers configured.
Backwards Compatibility Impact
This feature is only available on PHP 8.1 and later, and it is not possible to back-port this feature.
It is possible to explicitly set an IP address to a host with CURLOPT_RESOLVE
option, if the caller obtains the DNS information by using a DoH server with its own DNS querying.
Cross-version compatibility can be achieved by checking the CURLOPT_DOH_URL
constant:
$ch = curl_init();
if (defined('CURLOPT_DOH_URL')) {
curl_setopt($ch, CURLOPT_DOH_URL, 'https://dns.google/dns-query');
}
curl_exec($ch);
GD Extension: Font identifiers are `\GdFont` class objects
Prior to PHP 8.1, the imageloadfont
function from GD extension returned a font-identifier resource ID as an integer. In PHP 8.1 and later, the resource ID is migrated to a GdFont
class instance.
All functions that previously accepted a resource ID from imageloadfont
function now accept the new GdFont
class objects, making this an opaque change.
Resource to Object Migration
PHP is gradually phasing out allresource
types with class objects, and this migration is one step of the Resource to Object Migration plan.
imageloadfont
function return value is changed from int
to GdImage
. This is different from other resource to object migrations that replaced a resource
with a class instance.
gd
resource for primitive GD images was migrated toGdImage
class instances in PHP 8.0:GdImage
class objects replace GD image resources
GdFont
class synopsis
final class GdFont {}
Directly instantiating a GdFont
instance is not allowed, and it is still required to use the imageloadfont
:
$font = new GdFont();
PHP Error: You cannot initialize a GdFont object except through helper functions in ... on line ...
GdFont
class is also declared final
, to prevent it from being extended with child classes, to reduce potential backwards-compatibility issues if the GdFont
class structure changes in the future.
Backwards Compatibility Impact
Prior to this change in PHP 8.1, the GD Font resources were int
values. All code with parameter/return/property type with this assumption now needs to accommodate GdFont
class instances.
In PHP 8.0, Union Types can enforce this requirement.
- function addFont(int $gdfont){}
+ function addFont(int|GdFont $gdfont) {}
imageloadfont
function continues to return false
in case the font file could not be loaded, and a check against false
value might be more readable and appropriate:
$font = imageloadfont($file);
- if (is_int($font)) {
+ if ($font !== false) {
//
}
FTP Extension: Connection resources are `\FTPConnection` class objects
FTP connection resources from the ftp
extension are migrated to class objects in PHP 8.1.
Prior to PHP 8.1, FTP connections created with ftp_connect()
, and ftp_ssl_connect()
functions returned a resource
of type ftp
. In PHP 8.1 and later, those functions return a FTPConnection
class instance instead.
All functions that previously accepted resources accept the new type as well.
Resource to Object Migration
PHP is gradually phasing out allresource
types with class objects, and this migration is one step of the Resource to Object Migration plan.
FTPConnection Class Synopsis
final class FTPConnection {}
Similar to other resource
to object migrations, the new FTPConnection
class is declared final
to prevent them from extended (and reduce potential backwards-compatibility issues if PHP core changes the methods of the FTPConnection
class).
Instantiating a new instance of FTPConnection
is not allowed, and results in an \Error
Exception.
new FTPConnection();
Cannot directly construct FTPConnection, use ftp_connect() or ftp_ssl_connect() instead
is_resource
checks
User-land PHP code that work with ftp
resources sometimes check for the validity of the resource
object with is_resource
function.
This was not necessary because ftp_connect
and ftp_ssl_connect
functions return false
on error. This pattern is maintained in PHP 8.1 too.
To check if creation of an FTP resource was successful, a simple check for false
is cross-version compatible, and is just as accurate as a is_resource
check.
Existing code that relied on is_resource
function now needs to account for the new FTPConnection
class objects in PHP 8.1.
- is_resource($ftp_connection)
+ is_resource($ftp_connection) || $ftp_connection instanceof \FTPConnection
Alternately, a check against false
might be more appropriate and more readable.
- is_resource($ftp_connection)
+ $ftp_connection !== false
Destroying FTPConnection
objects
Destroying an FTPConnection
object, or leaving it for the garbage collector to clean up will automatically close the FTP connection.
It is still possible to explicitly close the FTP connection with ftp_close
function. This function is not deprecated and is still functional.
Attempting to use a closed FTP connection object will throw a ValueError
exception.
ValueError: FTPConnection is already closed
Backwards Compatibility Impact
Similar to other resource to object migrations, FTP extension seamless upgrades return and accepted parameter types to the new object-based resources.
is_resource
function now returns false
instead of the previous true
return value for FTP connection objects. This might be a backwards-compatibility issue if the FTP connection resources are checked with that function.
IMAP: `imap` resources are `\IMAPConnection` class objects
In PHP 8.1, IMAP connection resources from the imap
extension are migrated to class objects.
Prior to PHP 8.1, imap_open() function returned a resource
object of type imap
. From PHP 8.1 and later, it returns an \IMAPConnection
class instance instead.
All functions that previously accepted resources accept the new type as well.
Resource to Object Migration
PHP is gradually phasing out allresource
types with class objects, and this migration is one step of the Resource to Object Migration plan.
IMAPConnection Class Synopsis
final class IMAPConnection {}
In line with other resource
to object migration semantics, the new IMAPConnection
class is declared final
as well, to minimize the potential backwards-compatibility breaks when PHP modifies the implementation details of the class.
imap_open
function is still used to create IMAPConnection
class instances, and instantiating with new IMAPConnection()
construct is not allowed:
new IMAPConnection();
Cannot directly construct IMAPConnection, use imap_open() instead
is_resource
checks
All resource
to object migrated class objects no longer return true
for is_resource
function, and any code that used is_resource
function to check the validity of an IMAP connection resource will cause problems in PHP 8.1 and later.
In all PHP versions, imap_open
function returns false
in case it failed to create an IMAP resource. This is still the case for PHP 8.1 and later as well.
Existing code that needs to check the validity of a given variable for an IMAP connection needs to account for IMAPConnection
class object return values in PHP 8.1 and later.
- is_resource($imap_connection)
+ is_resource($imap_connection) || $imap_connection instanceof \IMAPConnection
Alternately, a check against false
might be more appropriate and more readable, and works across all PHP versions too.
- is_resource($imap_connection)
+ $imap_connection !== false
Closing IMAPConnection
resources
IMAP connections are automatically closed when the IMAPConnection
is no longer referenced (i.e fall out of scope), or when explicitly destroyed with unset($connection)
.
imap_close
function, that was used to close imap
resources is still available and functional. Attempting to use a closed IMAPConnection
object results in a \ValueError
exception.
ValueError: IMAPConnection is already closed
Backwards Compatibility Impact
Following other resource to object migrations, IMAP extension seamless upgrades return and accepted parameter types to the new object-based resources.
is_resource
function now returns false
instead of the previous true
return value for IMAP connection objects. This might be a backwards-compatibility issue if the IMAP connection resources are checked with that function. See is_resource
checks section for possible workarounds
finfo Extension: `file_info` resource are migrated to existing `finfo` objects
finfo
extension has gone through the Resource to Object Migration, that the procedural API in the finfo
extension transparently returns and accepts finfo
class instances.
Resource to Object Migration
PHP is gradually phasing out allresource
types with class objects, and this migration is one step of the Resource to Object Migration plan.
In PHP 8.1, the following functions from finfo
extension return and accept finfo
class instances instead of resource
objects with type file_info
.
finfo
class
finfo
class already exists in all PHP versions (PHP >= 5.3), but prior to PHP 8.1, the procedural-style finfo_*
functions returned/accepted resources instead. From PHP 8.1, it is even possible to interchange the procedural return values with Object-Oriented style objects.
The finfo
class continues to function with same semantics as before, and there are no changes in finfo
class.
Destructing finfo
class instances
The finfo_close
function is still available, and is not deprecated.
Calling finfo_close
no longer has any effect. It is possible to continue to use a closed resource, which was not possible prior to PHP 8.0.
$f = finfo_open();
finfo_close($f);
finfo_file($f, 'test.txt'); // Allowed in PHP 8.1, but not prior.
Prior to PHP 8.1, using a closed resource
was not allowed, and the snippet above would have resulted in an error:
TypeError: finfo_file(): supplied resource is not a valid file_info resource
In PHP 8.1 and later, because the finfo_close
function has no effect, this pattern does not result in an error.
finfo
objects will be automatically destroyed when they fall out of scope. It is possible to destroy them explicitly as well:
unset($finfo);
Backwards Compatibility Impact
Similar to other Resource to Object Migration changes, the use of is_resource
function on return value of finfo_open
no longer returns true
. It returns false
instead.
To accept external finfo
instances, it is now necessary to account for finfo
class instances as well for compatibility with older PHP versions:
- is_resource($finfo)
+ is_resource($finfo) || $finfo instanceof finfo
Upon resource/object instantiation, a check against !== false
is sufficient as well:
$finfo = finfo_open();
- is_resource($finfo);
+ $finfo !== false
Alternately, a full-on migration to the OOP-style finfo
class might be an easier upgrade, as it has no effective changes at all.
Apart from the is_resource
function, the rest of the finfo
API remains opaque, because all functions that previously accepted a file_info
resource now accept finfo
class objects.
Pspell Extension: `pspell`, `pspell config` resources are `\PSpell`, `\PSpellConfig` class objects
All resource types from Pspell
extension are migrated to opaque class objects in PHP 8.1.
Prior to PHP 8.1, Pspell
extension used resource object types pspell
and pspell config
with an integer identifier. These two identifier values are replaced with class objects named PSpell
and PSpellConfig
.
All functions that previously returned and accepted resource integer identifiers now accept their corresponding class objects.
Resource to Object Migration
PHP is gradually phasing out allresource
types with class objects, and this migration is one step of the Resource to Object Migration plan.
PSpell Class
PSpell
class replaces the resource
objects of type pspell
. PHP returned an int
identifier for this prior to PHP 8.1, and returns a PSpell
class object in PHP 8.1 and later instead.
final class PSpell {}
All functions that returned or accepted pspell
object identifiers are changed to return/accept new PSpell
class objects.
The following functions are used to instantiate PSpell
objects. They previously returned pspell
resource identifiers as integers.
The following functions accepted pspell
resource object identifiers, and are changed to accept PSpell
class objects instead:
pspell_add_to_personal()
pspell_add_to_session()
pspell_check()
pspell_clear_session()
pspell_config_ignore()
pspell_config_mode()
pspell_config_personal()
pspell_config_repl()
pspell_config_runtogether()
pspell_config_save_repl()
pspell_save_wordlist()
pspell_store_replacement()
pspell_suggest()
Instantiating a PSpell
object with new PSpell
construct is not allowed:
new PSpell();
PHP Error: You cannot initialize a PSpell object except through helper functions in ... code on line ...
It is not allowed to serialize
a PSpell
object either:
serialize(pspell_new("en", "", "", ""));
Serialization of 'PSpell' is not allowed
PSpellConfig Class
The new PSpellConfig
class replaces pspell config
resource object identifiers (int
values).
final class PSpellConfig {}
Similar to all other resource
to class object migrations, all functions that returned/accepted pspell config
resource object identifiers now accept PSpellConfig
class instances.
-
pspell_config_create()
function previously returned andint
identifier forpspell config
resource objects, but returnPSpellConfig
class instances instead in PHP 8.1 and later. -
pspell_new_config()
function previously acceptedpspell config
resource objects identifiers asint
, and are changed to acceptPSpellConfig
class objects from PHP 8.1 and later.
It is not allowed to use new PSpellConfig
construct to instantiate a PSpellConfig
instance.
new PSpellConfig();
PHP Error: You cannot initialize a PSpellConfig object except through helper functions in ... code on line ...
Further, it is not allowed to serialize
a PSpell
object either.
serialize(pspell_config_create('en'));
Serialization of 'PSpellConfig' is not allowed
Destroying PSpell resources
Prior to PHP 8.1, there were no specific functions to forcefully close a pspell
/pspell config
resource. PHP automatically cleans them with garbage cleaning.
From PHP 8.1 and later, the returned PSpell
/PSpellConfig
objects follow general object garbage-cleaning mechanisms, and they will be cleared when the objects fall out of scope. It is possible to explicitly destroy an object with unset()
construct too.
Backwards Compatibility Impact
Prior to PHP 8.1, all pspell_*
functions returned/accepted resource objects as integer identifiers. They are now changed in PHP 8.1 and later to return class objects instead.
Failure to instantiate any object return false
, and this pattern is not changed in PHP 8.1.
It was possible to serialize
the return values from pspell_*
functions, erroneously so, prior to PHP 8.1, but the new class objects are not allowed to be serialize
d.
`version_compare` operator restrictions
In PHP 8.1, the version_compare()
function is made more strict with the operator parameter.
In PHP 8.0, this was made to throw a ValueError
exception if an unknown parameter was passed, as part of the internal function exception handling changes.
In PHP 8.1, this is further restricted to disallow partial values for operator parameter.
version_compare(1, 2, '');
version_compare(1, 2, '!');
version_compare(1, 2, 'g');
version_compare(1, 2, 'l');
version_compare(1, 2, 'n');
All of the statements above are were allowed in PHP 8.0, and are no longer allowed in PHP 8.1. These values are undocumented values from the documentation.
Note that PHP 8.0 started to throw
TypeError
for all invalid values. The operators disallowed in PHP 8.1 are due to internal partial matching changes.
In PHP 8.1, only the following operators are allowed:
==
,=
, andeq
!=
,<>
, andne
>
andgt
>=
andge
<
andlt
<=
andle
Backwards Compatibility Impact
PHP 8.0 started to disallow a vast majority of disallowed values, and this change in PHP 8.1 is a continuation of it. The disallowed values were not documented.
Using the correct values fixes the issue for all PHP versions.
Passing `null` to non-nullable internal function parameters is deprecated
In PHP, passing null
to a function/method parameter that is not declared nullable is not allowed. Prior to PHP 8.1, this restriction was only applied to user-land functions, and not internal functions.
From PHP 8.1, passing null
to a function/method parameter not declared nullable emits a deprecation notice. This is in preparation to remove this functionality to throw a \TypeError
exception in a future PHP version as it does already for user-land functions/methods.
For example, strlen
function expects the first parameter to be of type string
. By definition, it does not accept null
values (i.e. not declared as ?string
nor string|null
union type). For historical reasons, PHP allowed passing null
, for these parameters. This behavior is inconsistent from user-land functions/methods because they would have thrown a \TypeError
exception if null
is passed to a parameter type not declared as nullable.
strlen(null); // 0
Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated in ... on line ...
This behavior was allowed in the first place for historical reasons. PHP 7.0's introduction of scalar types did not allow null
for parameters unless the parameter was explicitly declared nullable. However, internal functions were not changed to strictly enforce scalar types to ease upgrading to PHP 7.0.
In PHP 8.0, internal functions were updated to throw \TypeError
and \ValueError
exceptions instead of warnings.
This deprecation notice will likely occur often in legacy code bases that relied on PHP to coerce the default value for a parameter from
null
.
Upgrading null
parameters to default values
Code that uses null
in a function/method call in place of the default value will need to replace the null
value with the default value from the function declaration.
For example, a strlen(null)
call emits a deprecation, and the sensible upgrade to this call would be strlen('')
.
To bring another example, preg_split
function has a third parameter int $limit = -1
. Passing null
to this parameter emits a deprecation notice in PHP 8.1, and the sensible upgrade to this call would be setting $limit
parameter to -1
.
Backwards Compatibility Impact
This is a backwards-compatibility breaking change. Updating the null
parameters to adhere to the expected type should fix the deprecation notice across all PHP versions.
Further, it may be possible for automated code fixing tools to update said null
parameters to the default values from the function declaration.