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ặcprivate
; hoặcvar
- Tất cả các loại đều được phép, ngoại trừ
void
Vàcallable
Đâ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ừ void
Và callable
.
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::method
như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)
# 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!