Hướng dẫn php 8.2 rfc

 Kết luận: Trên đây là các tính năng mới nhất của PHP 8, phiên bản mới phát hàng vào tháng 11.2020. Hy vọng các bạn có thể áp dụng các tính năng mới này trong quá trình làm việc với PHP 8. Tìm hiểu thêm về PHP và các ngôn ngữ lập trình khác qua khóa học lập trình tại T3H.

Show

I migrated an application to a platform without a local transport agent (MTA). I did not want to configure an MTA, so I wrote this xxmail function to replace mail() with calls to a remote SMTP server. Hopefully it is of some use.

function xxmail($to, $subject, $body, $headers)
{
$smtp = stream_socket_client('tcp://smtp.yourmail.com:25', $eno, $estr, 30);

$B = 8192;
$c = "\r\n";
$s = '[email protected]';

fwrite($smtp, 'helo ' . $_ENV['HOSTNAME'] . $c);
  $junk = fgets($smtp, $B);

// Envelope
fwrite($smtp, 'mail from: ' . $s . $c);
  $junk = fgets($smtp, $B);
fwrite($smtp, 'rcpt to: ' . $to . $c);
  $junk = fgets($smtp, $B);
fwrite($smtp, 'data' . $c);
  $junk = fgets($smtp, $B);

// Header
fwrite($smtp, 'To: ' . $to . $c);
if(strlen($subject)) fwrite($smtp, 'Subject: ' . $subject . $c);
if(strlen($headers)) fwrite($smtp, $headers); // Must be \r\n (delimited)
fwrite($smtp, $headers . $c);

// Body
if(strlen($body)) fwrite($smtp, $body . $c);
fwrite($smtp, $c . '.' . $c);
  $junk = fgets($smtp, $B);

// Close
fwrite($smtp, 'quit' . $c);
  $junk = fgets($smtp, $B);
fclose($smtp);
}

After lots of research and testing, I'd like to share my findings about my problems with Internet Explorer and file downloads.

  Take a look at this code, which replicates the normal download of a Javascript:

if(strstr($_SERVER["HTTP_USER_AGENT"],"MSIE")==false) {
  header("Content-type: text/javascript");
  header("Content-Disposition: inline; filename=\"download.js\"");
  header("Content-Length: ".filesize("my-file.js"));
} else {
  header("Content-type: application/force-download");
  header("Content-Disposition: attachment; filename=\"download.js\"");
  header("Content-Length: ".filesize("my-file.js"));
}
header("Expires: Fri, 01 Jan 2010 05:00:00 GMT");
if(strstr($_SERVER["HTTP_USER_AGENT"],"MSIE")==false) {
  header("Cache-Control: no-cache");
  header("Pragma: no-cache");
}
include("my-file.js");
?>

Now let me explain:

  I start out by checking for IE, then if not IE, I set Content-type (case-sensitive) to JS and set Content-Disposition (every header is case-sensitive from now on) to inline, because most browsers outside of IE like to display JS inline. (User may change settings). The Content-Length header is required by some browsers to activate download box. Then, if it is IE, the "application/force-download" Content-type is sometimes required to show the download box. Use this if you don't want your PDF to display in the browser (in IE). I use it here to make sure the box opens. Anyway, I set the Content-Disposition to attachment because I already know that the box will appear. Then I have the Content-Length again.

  Now, here's my big point. I have the Cache-Control and Pragma headers sent only if not IE. THESE HEADERS WILL PREVENT DOWNLOAD ON IE!!! Only use the Expires header, after all, it will require the file to be downloaded again the next time. This is not a bug! IE stores downloads in the Temporary Internet Files folder until the download is complete. I know this because once I downloaded a huge file to My Documents, but the Download Dialog box put it in the Temp folder and moved it at the end. Just think about it. If IE requires the file to be downloaded to the Temp folder, setting the Cache-Control and Pragma headers will cause an error!

I hope this saves someone some time!
~Cody G.

You will receive an email on last Wednesday of every month and on major PHP releases with new articles related to PHP, upcoming changes, new features and what's changing in the language. No marketing emails, no selling of your contacts, no click-tracking, and one-click instant unsubscribe from any email you receive.

PHP 8.2 is released on December 8, 2022. In this post, we'll go through all features, performance improvements, changes and deprecations one by one.

Readonly classes RFC

Readonly properties were introduced in PHP 8.1. This RFC builds on top of them, and adds syntactic sugar to make all class properties readonly at once. Instead of writing this:

class Post
{
    public function __construct(
        public readonly string $title, 
        public readonly Author $author,
        public readonly string $body,
        public readonly DateTime $publishedAt,
    ) {}
}

You can now write this:

readonly class Post
{
    public function __construct(
        public string $title, 
        public Author $author,
        public string $body,
        public DateTime $publishedAt,
    ) {}
}

Functionally, making a class readonly is entirely the same as making every property readonly; but it will also prevent dynamic properties being added on a class:

$post = new Post(/* … */);

$post->unknown = 'wrong';

Uncaught Error: Cannot create dynamic property Post::$unknown

Note that you can only extend from readonly classes if the child class is readonly as well.

PHP has changed quite a lot, and readonly classes are a welcome addition. You can take a look at my video about PHP's evolution if you want to as well:


Deprecate dynamic properties RFC

I'd say this is a change for the better, but it will hurt a little bit. Dynamic properties are deprecated in PHP 8.2, and will throw an

$post = new Post(/* … */);

$post->unknown = 'wrong';

Uncaught Error: Cannot create dynamic property Post::$unknown
1 in PHP 9.0:

class Post
{
    public string $title;
}

// …

$post->name = 'Name';

Keep in mind that classes implementing

$post = new Post(/* … */);

$post->unknown = 'wrong';

Uncaught Error: Cannot create dynamic property Post::$unknown
2 and
$post = new Post(/* … */);

$post->unknown = 'wrong';

Uncaught Error: Cannot create dynamic property Post::$unknown
3 will still work as intended:

class Post
{
    private array $properties = [];
    
    public function __set(string $name, mixed $value): void
    {
        $this->properties[$name] = $value;
    }
}

// …

$post->name = 'Name';

If you want to learn more about why deprecations are useful and how to deal with them, you can read this followup post on how to deal with deprecations, or you can check out my vlog:


New random extension RFC

PHP 8.2 adds a new random number generator that fixes a lot of problems with the previous one: it’s more performant, more secure, it’s easier to maintain, and doesn’t rely on global state; eliminating a range of difficult to detect bugs when using PHP’s random functions.

There’s a new class called

$post = new Post(/* … */);

$post->unknown = 'wrong';

Uncaught Error: Cannot create dynamic property Post::$unknown
4, which accepts a randomizer engine. Now you can change that engine, depending on your needs. For example, to differentiate between a production and testing environment.

$rng = $is_production
    ? new Random\Engine\Secure()
    : new Random\Engine\Mt19937(1234);
 
$randomizer = new Random\Randomizer($rng);
$randomizer->shuffleString('foobar');

$post = new Post(/* … */); $post->unknown = 'wrong'; Uncaught Error: Cannot create dynamic property Post::$unknown5, $post = new Post(/* … */); $post->unknown = 'wrong'; Uncaught Error: Cannot create dynamic property Post::$unknown6, and $post = new Post(/* … */); $post->unknown = 'wrong'; Uncaught Error: Cannot create dynamic property Post::$unknown7 as standalone types RFC

PHP 8.2 adds three new types — or something that looks like it. We'll avoid going down the rabbit hole of type safety in this post, but technically

$post = new Post(/* … */);

$post->unknown = 'wrong';

Uncaught Error: Cannot create dynamic property Post::$unknown
5,
$post = new Post(/* … */);

$post->unknown = 'wrong';

Uncaught Error: Cannot create dynamic property Post::$unknown
6, and
$post = new Post(/* … */);

$post->unknown = 'wrong';

Uncaught Error: Cannot create dynamic property Post::$unknown
7 could be considered valid types on their own. Common examples are PHP's built-in functions, where
$post = new Post(/* … */);

$post->unknown = 'wrong';

Uncaught Error: Cannot create dynamic property Post::$unknown
7 is used as the return type for when an error occurs. For example in
class Post
{
    public string $title;
}

// …

$post->name = 'Name';
2:

file_get_contents(/* … */): string|false

Before PHP 8.2, you could already use

$post = new Post(/* … */);

$post->unknown = 'wrong';

Uncaught Error: Cannot create dynamic property Post::$unknown
7 together with other types as a union; but now it can be used as a standalone type as well:

function alwaysFalse(): false
{
    return false;
}

The same now also goes for

$post = new Post(/* … */);

$post->unknown = 'wrong';

Uncaught Error: Cannot create dynamic property Post::$unknown
6 and
$post = new Post(/* … */);

$post->unknown = 'wrong';

Uncaught Error: Cannot create dynamic property Post::$unknown
5.


Disjunctive Normal Form Types RFC

DNF types allow us to combine and types, following a strict rule: when combining union and intersection types, intersection types must be grouped with brackets. In practice, that looks like this:

function generateSlug((HasTitle&HasId)|null $post) 
{
    if ($post === null) {
        return '';
    }

    return 
        strtolower($post->getTitle()) 
        . $post->getId();
}

In this case,

class Post
{
    public string $title;
}

// …

$post->name = 'Name';
6 is the DNF type.

It's a nice addition, especially since it means that we can now have nullable intersection types, which is probably the most important use case for this feature.


Constants in traits RFC

You can now use constants in traits:

trait Foo 
{
    public const CONSTANT = 1;
 
    public function bar(): int 
    {
        return self::CONSTANT;
    }
}

You won't be able to access the constant via the trait's name, either from outside the trait, or from inside it.

readonly class Post
{
    public function __construct(
        public string $title, 
        public Author $author,
        public string $body,
        public DateTime $publishedAt,
    ) {}
}
0

You can however access the constant via the class that uses the trait, given that it's public:

readonly class Post
{
    public function __construct(
        public string $title, 
        public Author $author,
        public string $body,
        public DateTime $publishedAt,
    ) {}
}
1

Redact parameters in back traces RFC

A common practice in any codebase is to send production errors to a service that keeps track of them, and will notify developers when something goes wrong. This practice often involves sending stack traces over the wire to a third party service. There are case however where those stack traces can include sensitive information such as environment variables, passwords or usernames.

PHP 8.2 allows you to mark such "sensitive parameters" with an attribute, so that you don't need to worry about them being listed in your stack traces when something goes wrong. Here's an example from the RFC:

readonly class Post
{
    public function __construct(
        public string $title, 
        public Author $author,
        public string $body,
        public DateTime $publishedAt,
    ) {}
}
2

Fetch properties of enums in const expressions RFC

From the RFC:

This RFC proposes to allow the use of

class Post
{
    public string $title;
}

// …

$post->name = 'Name';
7/
class Post
{
    public string $title;
}

// …

$post->name = 'Name';
8 to fetch properties of enums in constant expressions. The primary motivation for this change is to allow fetching the name and value properties in places where enum objects aren't allowed, like array keys

That means that the following code is now valid:

readonly class Post
{
    public function __construct(
        public string $title, 
        public Author $author,
        public string $body,
        public DateTime $publishedAt,
    ) {}
}
3

Return type changes for class Post { public string $title; } // … $post->name = 'Name';9 and class Post { private array $properties = []; public function __set(string $name, mixed $value): void { $this->properties[$name] = $value; } } // … $post->name = 'Name';0

Previously, these methods looked like this:

readonly class Post
{
    public function __construct(
        public string $title, 
        public Author $author,
        public string $body,
        public DateTime $publishedAt,
    ) {}
}
4

In PHP 8.2 those method signatures are changed like so:

readonly class Post
{
    public function __construct(
        public string $title, 
        public Author $author,
        public string $body,
        public DateTime $publishedAt,
    ) {}
}
5

This change makes a lot more sense, as it improves static insight possibilities for classes extending from

class Post
{
    private array $properties = [];
    
    public function __set(string $name, mixed $value): void
    {
        $this->properties[$name] = $value;
    }
}

// …

$post->name = 'Name';
1 and
class Post
{
    private array $properties = [];
    
    public function __set(string $name, mixed $value): void
    {
        $this->properties[$name] = $value;
    }
}

// …

$post->name = 'Name';
2. However, technically, this is a breaking change that might affect custom implementations that extend from either of those two classes.


class Post { private array $properties = []; public function __set(string $name, mixed $value): void { $this->properties[$name] = $value; } } // … $post->name = 'Name';3 and class Post { private array $properties = []; public function __set(string $name, mixed $value): void { $this->properties[$name] = $value; } } // … $post->name = 'Name';4 deprecations RFC

In PHP 8.2, using either

class Post
{
    private array $properties = [];
    
    public function __set(string $name, mixed $value): void
    {
        $this->properties[$name] = $value;
    }
}

// …

$post->name = 'Name';
3 or
class Post
{
    private array $properties = [];
    
    public function __set(string $name, mixed $value): void
    {
        $this->properties[$name] = $value;
    }
}

// …

$post->name = 'Name';
4 will trigger these deprecation notices:

readonly class Post
{
    public function __construct(
        public string $title, 
        public Author $author,
        public string $body,
        public DateTime $publishedAt,
    ) {}
}
6

The RFC argues that these functions have a inaccurate name that often causes confusion: these functions only convert between

class Post
{
    private array $properties = [];
    
    public function __set(string $name, mixed $value): void
    {
        $this->properties[$name] = $value;
    }
}

// …

$post->name = 'Name';
7 and
class Post
{
    private array $properties = [];
    
    public function __set(string $name, mixed $value): void
    {
        $this->properties[$name] = $value;
    }
}

// …

$post->name = 'Name';
8, while the function name suggest a more broader use. There's a more detailed explanation about the reasoning in the RFC.

The alternative? The RFC suggests using

class Post
{
    private array $properties = [];
    
    public function __set(string $name, mixed $value): void
    {
        $this->properties[$name] = $value;
    }
}

// …

$post->name = 'Name';
9 instead.


Locale-insensitive $rng = $is_production ? new Random\Engine\Secure() : new Random\Engine\Mt19937(1234); $randomizer = new Random\Randomizer($rng); $randomizer->shuffleString('foobar');0 and $rng = $is_production ? new Random\Engine\Secure() : new Random\Engine\Mt19937(1234); $randomizer = new Random\Randomizer($rng); $randomizer->shuffleString('foobar');1 RFC

Both

$rng = $is_production
    ? new Random\Engine\Secure()
    : new Random\Engine\Mt19937(1234);
 
$randomizer = new Random\Randomizer($rng);
$randomizer->shuffleString('foobar');
0 and
$rng = $is_production
    ? new Random\Engine\Secure()
    : new Random\Engine\Mt19937(1234);
 
$randomizer = new Random\Randomizer($rng);
$randomizer->shuffleString('foobar');
1 are no longer locale-sensitive. You can use
$rng = $is_production
    ? new Random\Engine\Secure()
    : new Random\Engine\Mt19937(1234);
 
$randomizer = new Random\Randomizer($rng);
$randomizer->shuffleString('foobar');
4 if you want localized case conversion.


Signature changes to several SPL methods

Several methods of SPL classes have been changed to properly enforce their correct type signature:

readonly class Post
{
    public function __construct(
        public string $title, 
        public Author $author,
        public string $body,
        public DateTime $publishedAt,
    ) {}
}
7

New $rng = $is_production ? new Random\Engine\Secure() : new Random\Engine\Mt19937(1234); $randomizer = new Random\Randomizer($rng); $randomizer->shuffleString('foobar');5 modifier in PCRE

You can now use the

$rng = $is_production
    ? new Random\Engine\Secure()
    : new Random\Engine\Mt19937(1234);
 
$randomizer = new Random\Randomizer($rng);
$randomizer->shuffleString('foobar');
5 modifier (
$rng = $is_production
    ? new Random\Engine\Secure()
    : new Random\Engine\Mt19937(1234);
 
$randomizer = new Random\Randomizer($rng);
$randomizer->shuffleString('foobar');
7) in
$rng = $is_production
    ? new Random\Engine\Secure()
    : new Random\Engine\Mt19937(1234);
 
$randomizer = new Random\Randomizer($rng);
$randomizer->shuffleString('foobar');
8 functions.


ODBC username and password escaping

From the guide:

The

$rng = $is_production
    ? new Random\Engine\Secure()
    : new Random\Engine\Mt19937(1234);
 
$randomizer = new Random\Randomizer($rng);
$randomizer->shuffleString('foobar');
9 extension now escapes the username and password for the case when both a connection string and username/password are passed, and the string must be appended to.

The same applies to

file_get_contents(/* … */): string|false
0.

Noticed a tpyo? You can submit a PR to fix it. If you want to stay up to date about what's happening on this blog, you can follow me on Twitter or subscribe to my newsletter:Email Subscribe