PHP 8 có gì mới?

798

Chào anh em,

PHP đã chính thức phát hành phiên bản 8.0 (26/11/2020), với nhiều cải tiến mới cả về hiệu năng lẫn cũ pháp. Trong bài viết này, chúng ta cùng review các điểm thay đổi có trong PHP 8 nhé.

Trích lời dẫn trên trang chủ PHP.net

PHP 8.0 là một phiên bản cập nhật lớn của PHP. Phiên bản này bao gồm rất nhiều tính năng mới, đồng thời tối ưu cách truyền tham số (có thể đặt tên khi truyền tham số), union types (một biến có thể thuộc một vài kiểu dữ liệu), attributes, constructor, biểu thức match (cú pháp mới, gần giống switch case), toán tử nullsafe (cho phép truy xuất giá trị null một cách an toàn), JIT (trình biên dịch mới, giúp PHP 8 đạt hiệu năng cao), và cải tiến các về type system, xử lý lỗi, và tính nhất quán.

Chúng ta sẽ tìm hiểu lần lượt về các sự thay đổi của PHP 8 so với phiên bản trước lần lượt qua các mục dưới đây.

I. Truyền tham số theo tên gọi – PHP 8

Việc một function có nhiều tham số (với mình là từ 3 tham số trở nên), có thể khiến developer lúng túng khi sử dụng vì không nhớ rõ ý nghĩa của từng tham số, cũng như thứ tự truyền của chúng.

Ví dụ, function mkdir() (function giúp tạo thư mục) trong PHP có 4 tham số lần lượt là:

  • $directory: Bắt buộc, là đường dẫn để tạo thư mục
  • $permissions: Không bắt buộc, là khả năng truy cập vào thư mục (kiểu 0777, hay 0655), mặc định là 0777.
  • $recursive: Không bắt buộc, có cho phép tạo thư mục con ngay cả khi thư mục cha không tồn tại (cho phép tạo kiểu đệ quy), mặc định là false.
  • $context: Không bắt buộc, còn ý nghĩa là gì thì mình cũng chẳng hiểu lắm, chưa bao giờ dùng đến tham số này.

Các vấn đề mà một developer có thể gặp phải khi sử dụng mkdir() đó là:

  • Đôi khi không nhớ rõ thứ tự của 2 tham số $permissions$recursive, không biết tham số nào được viết trước.
  • Nếu muốn đổi giá trị của tham số $recursive từ false thành true, developer buộc phải truyền cả tham số $permissions (vì $permissions đứng trước $recursive).

Cả hai vấn đề trên đều gây bất tiện khi sử dụng, để khắc phục nó, thì PHP cung cấp cú pháp mới cho phép truyền tham số theo tên gọi:

<?php

// PHP 7.x
mkdir('./hello/world', 0777, true);

// PHP 8
mkdir(recursive: true, directory: './hello/world');
// Hoặc
mkdir('./hello/world', recursive: true);

Lưu ý:
– Đây chỉ là cú pháp mới mà PHP 8 cung cấp để tiện sử dụng hơn khi cần, còn bạn vẫn có thể sử dụng cú pháp cũ bình thường trên phiên bản PHP 8 này.

– Tên tham số bạn không được phép “tự nghĩ ra” mà phải tuân theo tài liệu của PHP. Như trong ví dụ trên, recursivedirectory là 2 tham số mà mình buộc phải tuân theo tài liệu mà PHP cung cấp.

Để xem “tên chuẩn” các tham số, có 2 cách:

Cách 1: Xem tài liệu chính thức trên trang chủ php.net (lưu ý phải xem trên trang chính thức, các trang khác có thể viết không đúng).

Cách 2: Sử dụng ReflectionFunction.

<?php

// Muốn xem chi tiết các tham số của function mkdir
$refFunc = new ReflectionFunction('mkdir');

foreach ($refFunc->getParameters() as $param) {
    print $param . PHP_EOL;
}

/* Output
Parameter #0 [ <required> string $directory ]
Parameter #1 [ <optional> int $permissions = 0777 ]
Parameter #2 [ <optional> bool $recursive = false ]
Parameter #3 [ <optional> $context = null ]
*/

Lưu ý siêu quan trọng

Khuyên bạn nên sử dụng Cách 2 là chính xác nhất, Cách 1 không hẳn sẽ chính xác với mọi function. Như function mkdir trong ví dụ trên, thì tài liệu PHP viết sai 2 tên tham số là pathnamemode, đáng lẽ nó phải là directorypermissions mới đúng.

Tài liệu PHP ghi sai tên tham số, đáng lẽ phải là directory và permissions mới đúng.

II. Tối giản cách set thuộc tính từ contructor – PHP 8

Việc khởi tạo giá trị cho thuộc tính trong contructor của PHP 7 thường được viết như sau:

<?php

// PHP 7
class Point {
  public float $x;
  public float $y;
  public float $z;

  public function __construct(
    float $x = 0.0,
    float $y = 0.0,
    float $z = 0.0,
  ) {
    $this->x = $x;
    $this->y = $y;
    $this->z = $z;
  }
}

Nhưng với PHP 8, bạn có thể viết ngắn gọn thành

<?php

// PHP 8
class Point {
  public function __construct(
    public float $x = 0.0,
    public float $y = 0.0,
    public float $z = 0.0,
  ) {}
}

III. Union types – PHP 8

PHP ngày càng hoàn thiện hơn về độ chặt chẽ của dữ liệu, trong phiên bản PHP 7.4, chúng ta có thể khai báo kiểu dữ liệu cho thuộc tính của lớp, tuy nhiên nó vẫn gặp một vài bất tiện. Nhưng bất tiện này đã khắc phục trong phiên bản 8 với khái niệm Union types – Cho phép một thuộc tính có thể thuộc nhiều kiểu dữ liệu.

<?php

// PHP 7
class Number {
    // Giả sử có một biến $numeber, và ta mong muốn nó ở dạng số
    // Số thì có nhiều kiểu, số nguyên, số thập phân.
    private $number;

    // Nhưng hơi buồn
    // Chúng ta không thể chỉ ra biến $number chỉ được phép thuộc dạng số với PHP 7
    public function __construct($number)
    {
        $this->number = $number;
    }
}

// Dẫn đến việc khởi tạo class, ta vẫn có thể truyền string như thường
// Và không gặp một lỗi nào cả
new Number('NaN');

// PHP 8
class Number {
    // PHP 8 cho phép bạn khai báo biến $number chỉ được phép thuộc một trong 2 kiểu dữ liệu int hoặc float
    // Khắc phục vấn đề của PHP 7 nêu trên
    public function __construct(
        private int|float $number
    ) {}
}

// Nếu dùng thế này, bạn sẽ gặp lỗi
new Number('NaN');

IV. Nullsafe operator – PHP 8

Nullsafe operator – tạm dịch là Toán tử null an toàn. Cho phép bạn truy cập vào giá trị của một thuộc tính ở giá trị null mà không bị lỗi.

Ở các phiên bản PHP trước, để chắc chắn trước khi truy cập vào một giá trị, ta thường kiểm tra nó khác null để tránh lỗi, và nhìn nó có vẻ dài dòng như sau:

<?php

$country =  null;

if ($session !== null) {
  $user = $session->user;

  if ($user !== null) {
    $address = $user->getAddress();

    if ($address !== null) {
      $country = $address->country;
    }
  }
}

Nhưng với PHP 8, bạn có thể rút gọn thành:

<?php

$country = $session?->user?->getAddress()?->country;

Khi bất kỳ thuộc tính nào trong “chuỗi” $session?->user?->getAddress()?->country là null, thì $country sẽ nhận giá trị là null và không có lỗi nào xảy ra.

V. Biểu thức match – PHP 8

Một biểu thức mới khá tương đồng với lệnh switch case được đưa vào trong phiên bản PHP 8 lần này là match, nhưng có một số điểm khác biệt sau:

  • match là một biểu thức, nghĩa là giá trị của nó có thể được trả về, bạn có thể dễ dàng lưu nó vào một biến để xử lý cho tiện.
  • Mỗi một nhánh của match chỉ được xử lý trên một dòng, nghĩa là bạn không cần break
  • match so sánh chặt chẽ về kiểu dữ liệu, giống như bạn so sánh sử dụng === vậy.

Bạn có thể xem ví dụ sau để hiểu rõ hơn:

<?php

// PHP 7
switch (8.0) {
  case '8.0':
    $result = "Oh no!";
    break;
  case 8.0:
    $result = "This is what I expected";
    break;
}
echo $result; // Oh no!


// PHP 8
$result = match (8.0) {
  '8.0' => "Oh no!",
  8.0 => "This is what I expected",
};

echo $result; // This is what I expected

Lưu ý: match khá tương đồng với switch case, nhưng không thể thay thế hẳn switch case.

VI. Trình biên dịch JIT – PHP 8

Hiểu nhanh thì JIT (Just In Time) là một kỹ thuật được PHP tích hợp vào việc biên dịch code PHP thành mã máy, giúp PHP 8 đạt được hiệu năng cao gấp 1,5 – 2 lần so với PHP 7. Nhưng đừng vội mừng, hãy xem biểu đồ dưới đây và mình sẽ giải thích cho bạn hiểu:

Biểu đồ so sánh tốc độ của PHP khi có JIT và không có JIT.

Trong biểu đồ trên:

  • bench.php, micro_bech.php, N-body, Mandelbrot đều là các bài test trên ứng dụng PHP đơn giản.
  • Các bài test còn lại, lần lượt test trên các ứng dụng PHP có độ phức tạp tăng dần.
  • Đường màu đen, kẻ dọc ở biểu đồ trên là ngưỡng tối đa khi PHP không sử dụng JIT.

Từ biểu đồ trên, rút ra nhận xét rằng:

Chỉ có ứng dụng PHP đơn giản khi áp dụng JIT mới có hiệu năng cao, còn các ứng dụng PHP phức tạp, sử dụng các framework, cms phổ biến như WordPress, Symfony (Chắc laravel cũng không ngoại lệ) thì sử dụng JIT lại không đem lại nhiều lợi ích. Thậm chí như trường hợp của Symfony khi áp dụng JIT còn làm ứng dụng chạy chậm hơn so với lúc không áp dụng.

Để nói hết về JIT thì bài viết này là chưa đủ, mình sẽ trình bày chi tiết ở một bài viết khác về cách cài đặt, ưu nhược điểm, cũng như phân tích nên dùng JIT trong dự án thực tế hay không.

VII. Một số cập nhật khác trên PHP 8

Một số cập nhật điển hình khác trên PHP 8 như sau:

  • Thay vì sử dụng PHPDoc để chú thích, PHP 8 đã cung cấp thêm cấu trúc metadata.
<?php

// PHP 7
class PostsController
{
    /**
     * @Route("/api/posts/{id}", methods={"GET"})
     */
    public function get($id) { /* ... */ }
}

// PHP 8
class PostsController
{
    #[Route("/api/posts/{id}", methods: ["GET"])]
    public function get($id) { /* ... */ }
}
  • Toán tử @ vốn để “câm lặng lỗi” không còn được sử dụng ở PHP 8.
<?php

// Khi thêm @ phía trước, nếu function có thực hiện lỗi cũng sẽ không báo lỗi
// Nhưng đó đã là quá khứ rồi.
// Ở PHP 8 vẫn báo lỗi bình thường
@unlink('hello/world');
  • Cụm try {} catch () {} có thể không cần biến $exception trong catch.
<?php

// PHP 7
try {
    1/0;
} catch (\Exception) { // Sẽ báo lỗi cú pháp ở dòng này
    die('Something wrong');
}

// PHP 8
try {
    1/0;
} catch (\Exception) { // Chạy bình thường
    die('Something wrong');
}
  • Cho phép dấu phẩy ở cuối danh sách tham số.
<?php

// PHP 7
class Uri
{
    private function __construct(
        ?string $scheme,
        ?string $user,
        ?string $pass,
        ?string $host,
        ?int $port,
        string $path,
        ?string $query,
        ?string $fragment // <-- Tham số cuối cùng không được phép có dấu phẩy
    ) {
        ...
    }
}

// PHP 8
class Uri
{
    private function __construct(
        ?string $scheme,
        ?string $user,
        ?string $pass,
        ?string $host,
        ?int $port,
        string $path,
        ?string $query,
        ?string $fragment, // <-- Được phép viết dấu phẩy ở tham số cuối cùng
    ) {
        ...
    }
}
Bộ sưu tập áo thun cho dân IT, đủ các ngôn ngữ lập trình và hệ điều hành.
Click vào ảnh để xem.
QC Được tài trợ

VIII. Tổng kết

Trên là những cập nhật điển hình nhất của PHP 8, và bài viết của mình đã nêu được khoảng 80% những gì PHP cập nhật trong phiên bản lần này. Nếu muốn tìm hiểu chi tiết, bạn có thể đọc từ tài liệu chính thức của PHP.

Xin chào, hẹn gặp lại.