类型化类属性已在PHP 7.4中添加,并为PHP的类型系统提供了重大改进。这些更改是完全可选的,并且不破坏以前的版本。

在本文中,我们将深入研究该功能,但首先让我们首先总结最重要的要点:

  • 它们自PHP 7.4起可用,该版本于2019年11月发布。
  • 他们只在类 上使用,并且需要访问修饰符:publicprotectedprivate; 或var
  • 允许所有类型,除了voidcallable

他们的实际情况是这样的:

class Foo
{
    public int $a;

    public ?string $b = 'foo';

    private Foo $prop;

    protected static string $static = 'default';
}

如果不确定类型带来的好处,建议您先阅读这篇文章

未初始化

在看有趣的东西之前,有一个关于类型化属性的重要方面,这是必须首先讨论的。

尽管您可能会乍一看,但以下代码仍然有效:

class Foo
{
    public int $bar;
}

$foo = new Foo;

即使$bar的值在创建 Foo 对象后不是整数,PHP 仅在访问$bar时才会引发错误:

var_dump($foo->bar);

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

从错误消息中可以看到,有一种新的“变量状态”:未初始化。

如果$bar没有类型,则其值将为null。但是类型可以为空,因此无法确定是否设置了类型为空的属性,或者只是将其忘记了。这就是为什么添加了“未初始化”的原因。

关于未初始化,要记住四件事:

  • 您无法从未初始化的属性读取,否则将导致致命错误。
  • 由于访问属性时会检查未初始化状态,因此即使其类型为不可为空,您也可以使用未初始化属性创建对象。
  • 您可以先写入未初始化的属性,然后再读取它。
  • unset在类型化的属性上使用会使它未初始化,而对未类型化的属性进行unset则会使它初始化null

特别要注意的是,以下代码在构造对象之后设置了未初始化的,不可为空的属性,这是有效的

class Foo
{
    public int $a;
}

$foo = new Foo;

$foo->a = 1;

尽管仅在读取属性值时才检查未初始化状态,但在写入属性时会进行类型验证。这意味着您可以确保没有任何无效类型最终会成为属性的值.

默认值和构造函数

让我们仔细看看如何初始化键入的值。对于标量类型,可以提供一个默认值:

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

请注意,仅当类型实际为可为空时,才能将 null 用作默认值。这似乎是显而易见的,但允许以下情况的参数默认值存在一些遗留行为:

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

passNull(null);

幸运的是,类型属性不允许这种令人困惑的行为。

另请注意,不可能有object或class类型的默认值。应使用构造函数设置其默认值。

初始化类型化值的明显地方当然是构造函数:

class Foo
{
    private int $a;

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

但也请记住我之前提到的内容:在构造函数之外写入未初始化的属性是有效的。只要没有任何内容从属性中读取,就不会执行未初始化的检查。

类型的类型

那么究竟可以类型化什么以及如何类型化?我已经提到过,类型化属性仅在类中起作用(目前),并且它们需要访问修饰符或var它们前面的关键字

从可用类型开始,几乎可以使用除voidcallable之外的所有类型。

因为 void 表示缺少值,因此不能用于类型化。然而,callable是有点细微差别。

PHP中的“ callable” 可以这样写:

$callable = [$this, 'method'];

假设您有以下(无效)代码:

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)();

在此例中, $callable 指的是  private Bar::method, 但是它在 Foo环境中被调用. 因为这个问题, 它决定不添加 callable 支持.

这没什么大不了的 , 因为 Closure 是一个有效类型,   它将记住从哪里构造它的$this上下文。

顺便说一句,这是所有可用类型的列表:

  • bool
  • int
  • float
  • string
  • array
  • iterable
  • object
  • ? (nullable)
  • self & parent
  • Classes & interfaces

强制和严格类型

PHP是我们喜欢和讨厌的一种动态语言,它将尽可能地强制转换类型。假设您在期望整数的地方传递了一个字符串,PHP将尝试自动转换该字符串:

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

coerce('1'); // 1

相同的原则适用于类型化属性。以下代码有效,并将转换'1'1

class Bar
{
    public int $i;
}

$bar = new Bar;

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

如果您不喜欢这种行为,可以通过声明严格类型来禁用它:

declare(strict_types=1);

$bar = new Bar;

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

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

类型差异和继承

即使PHP 7.4引入了改进的类型差异,但类型化属性仍然不变。这意味着以下无效

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)

如果上面的示例似乎并不有意义,则应查看以下内容:

class Foo
{
    public self $prop;
}

class Bar extends Foo
{
    public self $prop;
}

self在运行代码之前,PHP将用它所引用的具体类在后台替换它。这意味着在此示例中将引发相同的错误。处理此问题的唯一方法是执行以下操作:

class Foo
{
    public Foo $prop;
}

class Bar extends Foo
{
    public Foo $prop;
}

说到继承,您可能会发现很难提出任何好的用例来覆盖继承属性的类型。

虽然我同意这种看法,但值得注意的是,可以更改继承属性的类型,但前提是访问修饰符也从 private 到 protected 或 public.。

以下代码有效:

class Foo
{
    private int $prop;
}

class Bar extends Foo
{
    public string $prop;
}

但是,不允许将类型从可为空的类型更改为不可为空或反之也是。

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)

还有更多!

就像本文开头所说的那样,类型化属性是PHP主要补充。关于它们还有很多要说的。我建议您阅读RFC,以了解所有整洁的小细节。

如果您不是PHP 7.4的新手,您可能想阅读所做的更改和添加的功能的完整列表。老实说,这是很长一段时间以来最好的发行版之一,值得您花时间!

来自https://stitcher.io/blog/typed-properties-in-php-74

PHP 7.4中的类型化属性
标签: