Thuộc tính được gõ trong PHP 7.4

  • Post category:lập trình


Thuộc tính lớp đã gõ đã được thêm vào PHP 7.4 và cung cấp một cải tiến lớn cho PHPhệ thống kiểu của Những thay đổi này hoàn toàn được chọn tham gia và không vi phạm các phiên bản trước.

Trong bài đăng này, chúng ta sẽ xem xét kỹ hơn về tính năng này, nhưng trước tiên hãy bắt đầu bằng cách tóm tắt những điểm quan trọng nhất:

  • Chúng có sẵn kể từ PHP 7.4dự kiến ​​ra mắt vào tháng 11 năm 2019
  • Chúng chỉ có sẵn trong các lớp và yêu cầu công cụ sửa đổi truy cập: public, protected hoặc private; hoặc var
  • Tất cả các loại đều được phép, ngoại trừ voidcallable

Đây là những gì họ trông giống như trong hành động:

class Foo
{
    public int $a;

    public ?string $b = 'foo';

    private Foo $prop;

    protected static string $static = 'default';
}

Nếu bạn không chắc chắn về lợi ích bổ sung của các loại, tôi khuyên bạn nên đọc bài đăng này trước.

# Chưa được khởi tạo

Trước khi xem nội dung thú vị, có một khía cạnh quan trọng về các thuộc tính được đánh máy cần được nói đến trước tiên.

Bất chấp những gì bạn có thể nghĩ ngay từ cái nhìn đầu tiên, đoạn mã sau vẫn hợp lệ:

class Foo
{
    public int $bar;
}

$foo = new Foo;

Mặc dù giá trị của $bar không phải là số nguyên sau khi tạo một đối tượng Foo, PHP sẽ chỉ báo lỗi khi $bar được truy cập:

var_dump($foo->bar);

Fatal error: Uncaught Error: Typed property Foo::$bar 
must not be accessed before initialization

Như bạn có thể đọc từ thông báo lỗi, có một loại “trạng thái biến” mới: chưa được khởi tạo.

Nếu như $bar không có loại, giá trị của nó chỉ đơn giản là null. Tuy nhiên, các loại có thể là null, do đó không thể xác định xem thuộc tính null được nhập đã được đặt hay đơn giản là bị lãng quên. Đó là lý do tại sao “chưa được khởi tạo” đã được thêm vào.

Có bốn điều quan trọng cần nhớ về chưa được khởi tạo:

  • Bạn không thể đọc từ các thuộc tính chưa được khởi tạo, làm như vậy sẽ gây ra lỗi nghiêm trọng.
  • Vì trạng thái chưa được khởi tạo được chọn khi truy cập một thuộc tính nên bạn có thể tạo một đối tượng có thuộc tính chưa được khởi tạo, mặc dù loại của nó là không thể rỗng.
  • Bạn có thể ghi vào thuộc tính chưa được khởi tạo trước khi đọc từ nó.
  • sử dụng unset trên một thuộc tính đã gõ sẽ làm cho nó chưa được khởi tạo, trong khi việc bỏ đặt một thuộc tính chưa được gõ sẽ làm cho nó null.

Đặc biệt lưu ý rằng đoạn mã sau, trong đó thuộc tính chưa được khởi tạo, không thể rỗng được đặt sau khi xây dựng đối tượng, là hợp lệ

class Foo
{
    public int $a;
}

$foo = new Foo;

$foo->a = 1;

Mặc dù trạng thái chưa khởi tạo chỉ được kiểm tra khi đọc giá trị của thuộc tính, nhưng việc xác thực kiểu được thực hiện khi ghi vào thuộc tính đó. Điều này có nghĩa là bạn có thể chắc chắn rằng sẽ không có loại không hợp lệ nào trở thành giá trị của thuộc tính.

Tính năng mới trong PHP 8.3

# Mặc định và hàm tạo

Chúng ta hãy xem xét kỹ hơn cách khởi tạo các giá trị đã gõ. Trong trường hợp loại vô hướng, có thể cung cấp giá trị mặc định:

class Foo
{
    public int $bar = 4;
    
    public ?string $baz = null;
    
    public array $list = (1, 2, 3);
}

Lưu ý rằng bạn chỉ có thể sử dụng null làm mặc định nếu loại thực sự là null. Điều này có vẻ hiển nhiên, nhưng có một số hành vi cũ với các thông số mặc định được cho phép như sau:

function passNull(int $i = null)
{ /* … */ }

passNull(null);

May mắn thay, hành vi khó hiểu này không được phép với các thuộc tính đã nhập.

Cũng lưu ý rằng không thể có giá trị mặc định với object hoặc các loại lớp. Bạn nên sử dụng hàm tạo để đặt giá trị mặc định của chúng.

Tất nhiên, nơi hiển nhiên để khởi tạo các giá trị đã nhập sẽ là hàm tạo:

class Foo
{
    private int $a;

    public function __construct(int $a)
    {
        $this->a = $a;
    }
}

Nhưng cũng hãy nhớ những gì tôi đã đề cập trước đây: việc ghi vào một thuộc tính chưa được khởi tạo, bên ngoài hàm tạo là hợp lệ. Miễn là không có gì được đọc từ một thuộc tính thì việc kiểm tra chưa được khởi tạo sẽ không được thực hiện.

# Các loại loại

Vậy chính xác những gì có thể được gõ và làm thế nào? Tôi đã đề cập rằng các thuộc tính đã nhập sẽ chỉ hoạt động trong các lớp (hiện tại) và chúng cần một công cụ sửa đổi truy cập hoặc var từ khóa trước mặt họ.

Đối với các loại có sẵn thì hầu hết các loại đều có thể sử dụng được, ngoại trừ voidcallable.

Bởi vì void có nghĩa là không có giá trị, điều đó có nghĩa là nó không thể được sử dụng để nhập một giá trị.
callable tuy nhiên có nhiều sắc thái hơn một chút.

Xem, một “có thể gọi được” trong PHP có thể được viết như vậy:

$callable = ($this, 'method');

Giả sử bạn có mã (bị hỏng) sau:

class Foo
{
    public callable $callable;
    
    public function __construct(callable $callable)
    { /* … */ }
}

class Bar
{
    public Foo $foo;
    
    public function __construct()
    {
        $this->foo = new Foo(($this, 'method'))
    }
    
    private function method()
    { /* … */ }
}

$bar = new Bar;

($bar->foo->callable)();

Trong ví dụ này, $callable đề cập đến sự riêng tư Bar::methodnhưng được gọi trong bối cảnh của Foo. Vì vấn đề này nên đã quyết định không thêm callable ủng hộ.

Tuy nhiên, điều đó cũng không có gì to tát, bởi vì Closure là một loại hợp lệ, sẽ ghi nhớ $this bối cảnh nơi nó được xây dựng.

Ngoài ra, đây là danh sách tất cả các loại có sẵn:

  • bool
  • int
  • trôi nổi
  • sợi dây
  • mảng
  • có thể lặp lại
  • sự vật
  • ? (vô giá trị)
  • bản thân và cha mẹ
  • Lớp học & giao diện

# Các loại cưỡng chế và nghiêm ngặt

PHP, là ngôn ngữ động mà chúng ta yêu và ghét, sẽ cố gắng ép buộc hoặc chuyển đổi loại bất cứ khi nào có thể. Giả sử bạn truyền một chuỗi nơi bạn mong đợi một số nguyên, PHP sẽ thử và tự động chuyển đổi chuỗi đó:

function coerce(int $i)
{ /* … */ }

coerce('1'); // 1

Các nguyên tắc tương tự áp dụng cho các thuộc tính được gõ. Đoạn mã sau hợp lệ và sẽ chuyển đổi '1' ĐẾN 1.

class Bar
{
    public int $i;
}

$bar = new Bar;

$bar->i = '1'; // 1

Nếu bạn không thích hành vi này, bạn có thể vô hiệu hóa nó bằng cách khai báo các loại nghiêm ngặt:

declare(strict_types=1);

$bar = new Bar;

$bar->i = '1'; // 1

Fatal error: Uncaught TypeError: 
Typed property Bar::$i must be int, string used

# Kiểu phương sai và kế thừa

Mặc dù PHP 7.4 được giới thiệu phương sai kiểu được cải thiện, các thuộc tính được gõ vẫn bất biến. Điều này có nghĩa là những điều sau đây không hợp lệ:

class A {}
class B extends A {}

class Foo
{
    public A $prop;
}

class Bar extends Foo
{
    public B $prop;
}

Fatal error: Type of Bar::$prop must be A (as in class Foo)

Nếu ví dụ trên có vẻ không quan trọng, bạn nên xem xét những điều sau:

class Foo
{
    public self $prop;
}

class Bar extends Foo
{
    public self $prop;
}

PHP sẽ thay thế lại self hậu trường với lớp cụ thể mà nó đề cập đến, trước khi chạy mã. Điều này có nghĩa là lỗi tương tự sẽ xảy ra trong ví dụ này. Cách duy nhất để xử lý nó là làm như sau:

class Foo
{
    public Foo $prop;
}

class Bar extends Foo
{
    public Foo $prop;
}

Nói về tính kế thừa, bạn có thể khó tìm ra bất kỳ trường hợp sử dụng tốt nào để ghi đè lên các loại thuộc tính được kế thừa.

Mặc dù tôi đồng ý với quan điểm đó, nhưng cần lưu ý rằng có thể thay đổi loại thuộc tính được kế thừa, nhưng chỉ khi công cụ sửa đổi quyền truy cập cũng thay đổi từ private ĐẾN protected hoặc public.

Mã sau đây là hợp lệ:

class Foo
{
    private int $prop;
}

class Bar extends Foo
{
    public string $prop;
}

Tuy nhiên, không được phép thay đổi loại từ nullable thành non-nullable hoặc đảo ngược.

class Foo
{
    public int $a;
    public ?int $b;
}

class Bar extends Foo
{
    public ?int $a;
    public int $b;
}

Fatal error: Type of Bar::$a must be int (as in class Foo)

Nhận thấy một tpyo? Bạn có thể gửi PR để sửa nó. Nếu bạn muốn cập nhật về những gì đang diễn ra trên blog này, bạn có thể Theo dõi danh sách gửi thư của tôi: gửi email đến [email protected] và tôi sẽ thêm bạn vào danh sách.

# Còn nữa!

Giống như tôi đã nói ở đầu bài viết này, các thuộc tính được đánh máy là một lớn lao Hơn nữa PHP. Còn rất nhiều điều để nói về họ. Tôi khuyên bạn nên đọc qua RFC để biết tất cả các chi tiết nhỏ gọn.

Nếu bạn chưa quen với PHP 7.4, bạn có thể muốn đọc danh sách đầy đủ các thay đổi đã thực hiện và các tính năng được thêm vào. Thành thật mà nói, đây là một trong những bản phát hành hay nhất trong một thời gian dài và đáng để bạn dành thời gian!

Cuối cùng, nếu bạn có bất kỳ suy nghĩ nào muốn chia sẻ về chủ đề này, tôi rất mong được nghe ý kiến ​​của bạn! Bạn có thể liên hệ với tôi qua Twitter hoặc e-mail.

Cho đến lần sau!



Trả lời