php没有属性的类型,详解 PHP 7.4 的类型属性

d0eedcadabee8948b8469edcf78753b2.png

PHP 7.4中增加了类型化类属性,对php的类型系统进行了重大改进。这些更改完全是自愿加入的,不会破坏以前的版本。

在本文中,我们将深入了解该功能,但首先让我们总结一下最重要的几点:

这些更改自PHP 7.4起可用,计划于2019年11月发布

它们仅在类中可用,并且需要访问修饰符:public、protected或private;或var

允许所有类型,但void和callable除外

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

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 值仍不是整数值的情况下,PHP 也只是会在访问 $bar 时才会报错:

var_dump($foo->bar);

Fatal error: Uncaught Error: Typed property Foo::$bar

must not be accessed before initialization

从错误消息中可以看出,出现了一种新的变量状态:未初始化(uninitialized)

$bar 属性无论是否声明了类型,值都可以为 null。因此,无法确定类型属性是否设置。这就是增加变量「未初始化」状态的原因。

未初始化有四个方面需要注意:

无法读取未初始化的属性,一旦这么做,将引发致命错误;

由于在访问属性时会检查未初始化状态,所以即使是不可为空的对象也可以使用未初始化属性;

在读取未初始化属性时候之前可以对其进行写入;

unset 操作会让类型属性变成未初始化状态,而非类型属性只会变成值为 null;

特别要注意在对象实例化之后设置未初始化的类型属性是合法的:

class Foo

{

public int $a;

}

$foo = new Foo;

$foo->a = 1; // 合法

$foo->a = null; // 非法

虽然只会在读取属性值时检查未初始化状态,但在写入属性时会进行类型验证。这意味着任何无效的属性值都不会被设置。

默认值和构造函数

让我们仔细看看如何初始化类型属性值。对于标量类型,可以直接提供一个默认值

class Foo

{

public int $bar = 4;

public ?string $baz = null;

// 错误写法 public string $baz = null;

public array $list = [1, 2, 3];

}

类型属性不能显示设置为 null,除非是可空类型。这看上去显而易见的,但是一些旧行为却允许这种操作

function passNull(int $i = null)

{ /* … */ }

passNull(null);

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

还要注意,属性类型的默认值不可能为对象或者类,你应当使用构造器来设置这些值。

最明显的用来设置默认值的地方就是构造函数

class Foo

{

private int $a;

public function __construct(int $a)

{

$this->a = $a;

}

}

但也要记住我之前提到的内容:在构造函数之外写入未初始化(uninitialized )的属性是有效的。只要没有读取属性值的操作,编译器就不会执行未初始化的相关检查。

类型

那么到底哪些类型可以指定,又如何指定呢?我已经提到了指定属性类型只能在类中进行(当前如此),并且它们需要一个访问修饰符或是属性前面的var关键字。

对于可用类型,几乎所有类型都可以使用,除了void和callable类型.

因为void意味着没有值,所以它不能用于指定一个值的类型也就说得过去了。然而callback就有一点细微不同了。

可见,PHP中的"callback"可以这样写

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

在此例中,$callback引用了私有的Bar::method,但是是在Foo的上下文中被调用的。基于这个问题,决定不添加callback类型的支持。

不过这并不是什么大问题,因为Closure(闭包)是一种有效类型,它会记住构建它的$this上下文。

顺带一说,以下是所有可用类型的列表:

bool

int

float

string

array

iterable

object

? (nullable)

self & parent

Classes & interfaces

强制和严格类型

PHP,是我们既喜欢又反感的动态语言,它会尽可能地强制或转换类型。假设你在一个期望接受int的地方传入字符串,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;

}

在运行代码之前,PHP将在背后用它所引用的具体实现类来替换self。这意味着在此本例中将抛出相同的错误。解决此问题的唯一方法是执行以下操作:

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,则可能需要阅读 完整列表 中所做的更改和添加的功能。老实说,这是很长一段时间以来最好的发行版之一,值得您花时间!

最后,如果您有关于此主题的任何想法,我很乐意听取您的意见!您可以通过 Twitter 或 e-mail 与我联系。

后会有期!

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接

我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值