PHP 类型化属性:三思而后行。
更新:属性钩子解决了本文中描述的问题,并将随 PHP 8.4 一起发布。🎉
PHP 的类型化属性 RFC已合并到主分支,将在 PHP 7.4 中可用。😍
但是,在你急于使用此功能并开始将所有现有模型类移植到此功能之前,你绝对应该阅读本文。
今天你可能会看到一些相当冗长且仪式感很强的内容,比如这样:
class Product
{
/**
* @var int
*/
private $price;
/**
* @var string
*/
private $description;
public function getPrice(): int
{
return $this->price;
}
public function setPrice(int $price)
{
$this->price = $price;
}
public function getDescription(): string
{
return $this->description;
}
public function setDescription(string $description)
{
$this->description = $description;
}
}
你或许期待着把它移植成一个简洁明了的故事,比如这样:
class Product
{
public int $price;
public string $description;
}
从表面上看,它们在功能上确实是等效的。
等等。
采用这项新功能会带来两大弊端。
如果想要用接口进行抽象化呢?
你不能。
一旦开始使用公共的、类型提示的属性,就不能再引入抽象,因此必须向后移植到 getter 和 setter。
换句话说,如果您选择公共的、类型提示的属性,您就选择了放弃任何当前或未来的抽象!
如果想添加验证功能呢?
比如说,设定最高价格或描述的最大字符串长度?
你不能。
一旦您选择了类型提示属性,就不能再添加类型提示属性支持的简单类型检查之外的任何新约束。
另请注意,对这两项非常典型的更改中的任何一项都将是未来的重大更改,因此这一点绝对需要考虑。
耻辱。
那么,我猜是要重构回类型提示属性吧?
不过,你仍然可以在内部获得类型检查的好处,对吧?
interface ProductInterface
{
public function getPrice(): int;
public function getDescription(): string;
}
class Product implements ProductInterface
{
private int $price;
private string $description;
public function getPrice(): int
{
return $this->price;
}
public function setPrice(int $price)
{
$this->price = $price;
}
public function getDescription(): string
{
return $this->description;
}
public function setDescription(string $description)
{
$this->description = $description;
}
}
嗯。
当然,但是通过 setter 方法中的类型提示,你已经获得了类型安全。
现在你有了两层类型检查。但这并不能真正让代码“类型安全加倍”,对吧?
就静态分析和IDE支持而言,两者并无本质区别。
使用类型提示属性的唯一真正优势在于,与php-doc代码块相比,语法略微简洁一些。(或许还能额外增加一层防止内部bug的保护。)
你打算使用文档块添加一些内联文档吗?如果是这样,你无论如何都需要文档块——而且文档块中的类型提示不是可选的,所以你将不得不重复定义所有类型。
此时,您可以再次删除这些属性类型提示。您之前的配置已经没问题了。
现在...
我发帖不是为了扫大家的兴,破坏大家的心情。
我只是想让你意识到,如果你为了简洁和方便而选择类型提示属性,你将面临严重的权衡取舍。
类型提示属性在那些并非仅仅是带有公共 getter 和 setter 的模型类中,确实能提供便捷的内部类型检查,这值得庆幸。只是它并不会立即带来你在其他语言中体验到的那种便捷和简洁。
这是重要的一步!
但要实现您所期望的简洁和便利,还需要两个功能。
接口中的属性
为了解决第一个权衡问题,我们需要在接口中添加对类型提示属性的支持。
例如:
interface ProductInterface
{
public int $price;
public string $description;
}
class Product implements ProductInterface
{
public int $price;
public string $description;
}
你在其他使用过类型提示属性的语言中也体验过这个功能吗?它们也有。
这听起来很简单,但它引发了很多有趣而又棘手的问题,我在这里就不深入探讨了。
当然,这仅仅解决了第一个权衡问题——记住,你也放弃了在类型提示属性支持的简单类型检查之外添加新约束的能力。
访问器
为了解决第二个权衡问题,我们需要在类和接口中添加对访问器的支持。
接下来我们要进入幻想世界了,但请耐心听我说完。
添加长度约束后$description,我们可能会得到类似这样的结果:
interface ProductInterface
{
public int $price;
public string $description;
}
class Product implements ProductInterface
{
public int $price;
private string $_description;
public string $description {
get {
return $this->_description;
}
set {
if (strlen($description) > 100) {
throw new RangeException();
}
$this->_description = $description;
}
}
}
再说,其他语言中那些类型提示属性带给你的美好感受?没错,它们都有这个特性。
总之,这个例子的重点在于展示如何混合使用访问器和公共属性,以及(最重要的是)如何在公共属性和访问器之间进行重构而不会破坏接口。
实际上,这项功能之前就有人提出过。2009年曾提出过,但未付诸表决;2012年再次提出,但被否决;2013年提出的一个类似方案也被否决了。
所以这绝对不是“理所当然”的——这是另一个需要非常仔细和深思熟虑的设计的功能,而且可能还需要很长时间才能实现。
剧终。
我希望这篇文章能帮助你理解为什么这项新功能既是朝着正确方向迈出的令人兴奋的一步,同时也是你在充分了解自己正在做出的权衡取舍之后应该使用的功能。
干杯😎✌
文章来源:https://dev.to/mindplay/php-typed-properties-think-twice-3824