PHP 属性挂钩
从现在开始,努力学习吧!本文《PHP 属性挂钩》主要讲解了等等相关知识点,我会在golang学习网中持续更新相关的系列文章,欢迎大家关注并积极留言建议。下面就先一起来看一下本篇正文内容吧,希望能帮到你!

介绍
php 8.4 将于 2024 年 11 月发布,并将带来一个很酷的新功能:属性挂钩。
在本文中,我们将了解什么是属性挂钩以及如何在 php 8.4 项目中使用它们。
顺便说一句,您可能还有兴趣查看我的另一篇文章,其中向您展示了 php 8.4 中添加的新数组函数。
什么是 php 属性挂钩?
属性挂钩允许您为类属性定义自定义 getter 和 setter 逻辑,而无需编写单独的 getter 和 setter 方法。这意味着您可以直接在属性声明中定义逻辑,这样您就可以直接访问属性(例如 $user->firstname),而不必记住调用方法(例如 $user->getfirstname() 和 $user->setfirstname()) .
您可以在 https://wiki.php.net/rfc/property-hooks 查看此功能的 rfc
如果您是 laravel 开发人员,当您阅读本文时,您可能会注意到钩子看起来与 laravel 模型中的访问器和修改器非常相似。
我非常喜欢属性挂钩功能的外观,我想当 php 8.4 发布时我将在我的项目中使用它。
要了解属性挂钩的工作原理,让我们看一些示例用法。
“获取”钩子
您可以定义一个 get 钩子,每当您尝试访问属性时都会调用该钩子。
例如,假设您有一个简单的 user 类,它在构造函数中接受名字和姓氏。您可能想要定义一个 fullname 属性,将名字和姓氏连接在一起。为此,您可以为 fullname 属性定义一个 get 挂钩:
readonly class user
{
public string $fullname {
get {
return $this->firstname.' '.$this->lastname;
}
}
public function __construct(
public readonly string $firstname,
public readonly string $lastname
) {
//
}
}
$user = new user(firstname: 'ash', lastname: 'allen');
echo $user->firstname; // ash
echo $user->lastname; // allen
echo $user->fullname; // ash allen
在上面的示例中,我们可以看到我们为 fullname 属性定义了一个 get 钩子,该钩子返回一个通过将firstname和lastname属性连接在一起计算得出的值。我们也可以使用类似于箭头函数的语法来进一步清理它:
readonly class user
{
public string $fullname {
get => $this->firstname.' '.$this->lastname;
}
public function __construct(
public readonly string $firstname,
public readonly string $lastname,
) {
//
}
}
$user = new user(firstname: 'ash', lastname: 'allen');
echo $user->firstname; // ash
echo $user->lastname; // allen
echo $user->fullname; // ash allen
类型兼容性
需要注意的是,getter 的返回值必须与属性的类型兼容。
如果未启用严格类型,则该值将根据属性类型进行类型转换。例如,如果从声明为字符串的属性返回整数,则该整数将转换为字符串:
declare(strict_types=1);
class user
{
public string $fullname {
get {
return 123;
}
}
public function __construct(
public readonly string $firstname,
public readonly string $lastname,
) {
//
}
}
$user = new user(firstname: 'ash', lastname: 'allen');
echo $user->fullname; // "123"
在上面的例子中,即使我们指定了 123 作为要返回的整数,但“123”还是以字符串形式返回,因为该属性是字符串。
我们可以添加declare(strict_types=1);像这样添加到代码顶部以启用严格的类型检查:
declare(strict_types=1);
class user
{
public string $fullname {
get {
return 123;
}
}
public function __construct(
public readonly string $firstname,
public readonly string $lastname,
) {
//
}
}
现在这会导致抛出错误,因为返回值是整数,但属性是字符串:
fatal error: uncaught typeerror: user::$fullname::get(): return value must be of type string, int returned
“设置”钩子
php 8.4 属性钩子还允许您定义集合钩子。每当您尝试设置属性时都会调用此函数。
您可以为 set hook 在两种单独的语法之间进行选择:
- 显式定义要在属性上设置的值
- 使用箭头函数返回要在属性上设置的值
让我们看看这两种方法。我们想象一下,当在 user 类上设置名字和姓氏的首字母时,我们想要将它们设置为大写:
declare(strict_types=1);
class user
{
public string $firstname {
// explicitly set the property value
set(string $name) {
$this->firstname = ucfirst($name);
}
}
public string $lastname {
// use an arrow function and return the value
// you want to set on the property
set(string $name) => ucfirst($name);
}
public function __construct(
string $firstname,
string $lastname
) {
$this->firstname = $firstname;
$this->lastname = $lastname;
}
}
$user = new user(firstname: 'ash', lastname: 'allen');
echo $user->firstname; // ash
echo $user->lastname; // allen
正如我们在上面的示例中所看到的,我们为firstname 属性定义了一个set hook,在将名称设置为属性之前,该钩子将名称的第一个字母大写。我们还为 lastname 属性定义了一个 set hook,它使用箭头函数返回要在属性上设置的值。
类型兼容性
如果属性有类型声明,那么它的 set hook 也必须有兼容的类型集。下面的示例将返回错误,因为 firstname 的 set hook 没有类型声明,但属性本身有 string 的类型声明:
class user
{
public string $firstname {
set($name) => ucfirst($name);
}
public string $lastname {
set(string $name) => ucfirst($name);
}
public function __construct(
string $firstname,
string $lastname
) {
$this->firstname = $firstname;
$this->lastname = $lastname;
}
}
尝试运行上面的代码将导致抛出以下错误:
fatal error: type of parameter $name of hook user::$firstname::set must be compatible with property type
一起使用“get”和“set”钩子
您不限于单独使用 get 和 set 挂钩。您可以在同一房产中一起使用它们。
举个简单的例子。我们假设我们的 user 类有一个 fullname 属性。当我们设置属性时,我们会将全名分为名字和姓氏。我知道这是一种幼稚的方法,并且有更好的解决方案,但这纯粹是为了举例来突出显示挂钩属性。
代码可能看起来像这样:
declare(strict_types=1);
class user
{
public string $fullname {
// dynamically build up the full name from
// the first and last name
get => $this->firstname.' '.$this->lastname;
// split the full name into first and last name and
// then set them on their respective properties
set(string $name) {
$splitname = explode(' ', $name);
$this->firstname = $splitname[0];
$this->lastname = $splitname[1];
}
}
public string $firstname {
set(string $name) => $this->firstname = ucfirst($name);
}
public string $lastname {
set(string $name) => $this->lastname = ucfirst($name);
}
public function __construct(string $fullname) {
$this->fullname = $fullname;
}
}
$user = new user(fullname: 'ash allen');
echo $user->firstname; // ash
echo $user->lastname; // allen
echo $user->fullname; // ash allen
在上面的代码中,我们定义了一个 fullname 属性,它同时具有 get 和 set 钩子。 get 挂钩通过将名字和姓氏连接在一起来返回全名。 set 钩子将全名拆分为名字和姓氏,并将它们设置在各自的属性上。
您可能还注意到,我们没有为 fullname 属性本身设置值。相反,如果我们需要读取 fullname 属性的值,则会调用 get 挂钩以根据名字和姓氏属性构建全名。我这样做是为了强调,您可以拥有一个不直接设置值的属性,而是根据其他属性计算该值。
在升级属性上使用属性挂钩
属性挂钩的一个很酷的功能是您还可以将它们与构造函数提升的属性一起使用。
让我们看一个不使用提升属性的类的示例,然后看看使用提升属性时它会是什么样子。
我们的用户类可能看起来像这样:
readonly class user
{
public string $fullname {
get => $this->firstname.' '.$this->lastname;
}
public string $firstname {
set(string $name) => ucfirst($name);
}
public string $lastname {
set(string $name) => ucfirst($name);
}
public function __construct(
string $firstname,
string $lastname,
) {
$this->firstname = $firstname;
$this->lastname = $lastname;
}
}
我们可以将firstname和lastname属性提升到构造函数中,并直接在属性上定义它们的设置逻辑:
readonly class user
{
public string $fullname {
get => $this->firstname.' '.$this->lastname;
}
public function __construct(
public string $firstname {
set (string $name) => ucfirst($name);
},
public string $lastname {
set (string $name) => ucfirst($name);
}
) {
//
}
}
只写挂钩属性
如果您使用 setter 定义了一个挂钩属性,但实际上并未在该属性上设置值,则该属性将是只写的。这意味着你无法读取属性的值,只能设置它。
让我们采用前面示例中的 user 类,并通过删除 get 挂钩将 fullname 属性修改为只写:
declare(strict_types=1);
class user
{
public string $fullname {
// define a setter that doesn't set a value
// on the "fullname" property. this will
// make it a write-only property.
set(string $name) {
$splitname = explode(' ', $name);
$this->firstname = $splitname[0];
$this->lastname = $splitname[1];
}
}
public string $firstname {
set(string $name) => $this->firstname = ucfirst($name);
}
public string $lastname {
set(string $name) => $this->lastname = ucfirst($name);
}
public function __construct(
string $fullname,
) {
$this->fullname = $fullname;
}
}
$user = new user('ash allen');
echo $user->fullname; // will trigger an error!
如果我们运行上面的代码,我们会在尝试访问 fullname 属性时看到抛出以下错误:
fatal error: uncaught error: property user::$fullname is write-only
只读挂钩属性
同样,属性也可以是只读的。
例如,假设我们只希望从firstname 和lastname 属性生成fullname 属性。我们不想允许直接设置 fullname 属性。我们可以通过从 fullname 属性中删除 set 钩子来实现这一点:
class user
{
public string $fullname {
get {
return $this->firstname.' '.$this->lastname;
}
}
public function __construct(
public readonly string $firstname,
public readonly string $lastname,
) {
$this->fullname = 'invalid'; // will trigger an error!
}
}
如果我们尝试运行上面的代码,则会抛出以下错误,因为我们试图直接设置 fullname 属性:
uncaught error: property user::$fullname is read-only
使用“readonly”关键字
即使我们的 php 类具有挂钩属性,您仍然可以将它们设置为只读。例如,我们可能想让 user 类只读:
readonly class user
{
public string $firstname {
set(string $name) => ucfirst($name);
}
public string $lastname {
set(string $name) => ucfirst($name);
}
public function __construct(
string $firstname,
string $lastname,
) {
$this->firstname = $firstname;
$this->lastname = $lastname;
}
}
但是,hook 属性不能直接使用 readonly 关键字。例如,这个类将是无效的:
class user
{
public readonly string $fullname {
get => $this->firstname.' '.$this->lastname;
}
public function __construct(
string $firstname,
string $lastname,
) {
$this->firstname = $firstname;
$this->lastname = $lastname;
}
}
上面的代码会抛出以下错误:
fatal error: hooked properties cannot be readonly
“property”魔法常数
在 php 8.4 中,引入了一个名为 __property__ 的新魔法常量。该常量可用于引用属性挂钩内的属性名称。
让我们看一个例子:
class user
{
// ...
public string $lastname {
set(string $name) {
echo __property__; // lastname
$this->{__property__} = ucfirst($name); // will trigger an error!
}
}
public function __construct(
string $firstname,
string $lastname,
) {
$this->firstname = $firstname;
$this->lastname = $lastname;
}
}
在上面的代码中,我们可以看到在lastname属性的setter中使用__property__将会输出属性名称lastname。然而,还值得注意的是,尝试使用此常量来尝试设置属性值将触发错误:
fatal error: uncaught error: must not write to virtual property user::$lastname
有一个关于 __property__ 魔法常量的方便用例示例,您可以在 github 上查看:https://github.com/crell/php-rfcs/blob/master/property-hooks/examples.md。
接口中的挂钩属性
php 8.4 还允许您在接口中定义可公开访问的挂钩属性。如果您想强制类使用钩子实现某些属性,这会很有用。
让我们看一下声明了挂钩属性的示例接口:
interface nameable
{
// expects a public gettable 'fullname' property
public string $fullname { get; }
// expects a public gettable 'firstname' property
public string $firstname { get; }
// expects a public settable 'lastname' property
public string $lastname { set; }
}
在上面的接口中,我们定义任何实现 nameable 接口的类都必须具有:
- 至少可公开获取的 fullname 属性。这可以通过定义 get hook 或根本不定义 hook 来实现。
- 至少可公开获取的firstname 属性。
- 至少可公开设置的姓氏属性。这可以通过定义具有设置钩子的属性或根本不定义钩子来实现。但如果该类是只读的,那么该属性必须有一个设置的钩子。
这个实现 nameable 接口的类是有效的:
class user implements nameable
{
public string $fullname {
get => $this->firstname.' '.$this->lastname;
}
public string $firstname {
set(string $name) => ucfirst($name);
}
public string $lastname;
public function __construct(
string $firstname,
string $lastname,
) {
$this->firstname = $firstname;
$this->lastname = $lastname;
}
}
上面的类是有效的,因为 fullname 属性有一个 get 钩子来匹配接口定义。 firstname 属性只有一个 set hook,但仍然可以公开访问,因此它满足条件。 lastname 属性没有 get 挂钩,但它是可公开设置的,因此它满足条件。
让我们更新 user 类以强制执行 fullname 属性的 get 和 set 挂钩:
interface nameable
{
public string $fullname { get; set; }
public string $firstname { get; }
public string $lastname { set; }
}
我们的 user 类将不再满足 fullname 属性的条件,因为它没有定义 set hook。这会导致抛出以下错误:
fatal error: class user contains 1 abstract methods and must therefore be declared abstract or implement the remaining methods (nameable::$fullname::set)
抽象类中的挂钩属性
与接口类似,你也可以在抽象类中定义钩子属性。如果您想提供一个定义子类必须实现的挂钩属性的基类,这可能很有用。您还可以在抽象类中定义钩子,并在子类中覆盖它们。
例如,让我们创建一个 model 抽象类,定义一个必须由子类实现的 name 属性:
abstract class model
{
abstract public string $fullname {
get => $this->firstname.' '.$this->lastname;
set;
}
abstract public string $firstname { get; }
abstract public string $lastname { set; }
}
在上面的抽象类中,我们定义任何扩展 model 类的类都必须具有:
- 至少可公开获取和设置的 fullname 属性。这可以通过定义 get 和 set 钩子或根本不定义钩子来实现。我们还在抽象类中定义了 fullname 属性的 get 钩子,因此我们不需要在子类中定义它,但如果需要,可以覆盖它。
- 至少可公开获取的firstname 属性。这可以通过定义 get hook 或根本不定义 hook 来实现。
- 至少可公开设置的姓氏属性。这可以通过定义具有设置钩子的属性或根本不定义钩子来实现。但如果该类是只读的,那么该属性必须有一个设置的钩子。
然后我们可以创建一个扩展 model 类的 user 类:
class User extends Model
{
public string $fullName;
public string $firstName {
set(string $name) => ucfirst($name);
}
public string $lastName;
public function __construct(
string $firstName,
string $lastName,
) {
$this->firstName = $firstName;
$this->lastName = $lastName;
}
}
结论
希望本文能让您深入了解 php 8.4 属性挂钩的工作原理以及如何在 php 项目中使用它们。
如果这个功能一开始看起来有点令人困惑,我也不会太担心。当我第一次看到它时,我也有点困惑(特别是它们如何与接口和抽象类一起工作)。但一旦你开始修补它们,你很快就会掌握它的窍门。
我很高兴看到这个功能将如何在野外使用,我期待着 php 8.4 发布时在我的项目中使用它。
如果您喜欢阅读这篇文章,您可能有兴趣查看我的 220 多页电子书“battle ready laravel”,其中更深入地涵盖了类似的主题。
或者,您可能想查看我的另一本 440 多页电子书“consuming apis in laravel”,它教您如何使用 laravel 来使用来自其他服务的 api。
如果您有兴趣在我每次发布新帖子时获得更新,请随时订阅我的时事通讯。
继续创造精彩的东西! ?
理论要掌握,实操不能落!以上关于《PHP 属性挂钩》的详细介绍,大家都掌握了吧!如果想要继续提升自己的能力,那么就来关注golang学习网公众号吧!
使用住宅代理解决机器人流量挑战:识别、使用和检测指南
- 上一篇
- 使用住宅代理解决机器人流量挑战:识别、使用和检测指南
- 下一篇
- 上海市消保委:厂商不能用权限对其他渠道软件进行不合理限制,消费者有知情权
-
- 文章 · php教程 | 7小时前 |
- PHP启用Xdebug配置详细教程
- 438浏览 收藏
-
- 文章 · php教程 | 7小时前 |
- PHP内存限制调整方法与优化技巧
- 276浏览 收藏
-
- 文章 · php教程 | 8小时前 |
- TP模型CURD操作全攻略
- 264浏览 收藏
-
- 文章 · php教程 | 8小时前 |
- PHPmicrotime计算耗时及精度优化方法
- 430浏览 收藏
-
- 文章 · php教程 | 8小时前 |
- PHP控制RGB灯颜色的实现方法
- 254浏览 收藏
-
- 文章 · php教程 | 8小时前 | php 视频上传
- PHP上传进度显示实现方法
- 364浏览 收藏
-
- 文章 · php教程 | 8小时前 | php如何加密解密
- 云路PHP解密工具使用教程与评测
- 304浏览 收藏
-
- 文章 · php教程 | 9小时前 |
- PHP表单数组提交与获取方法
- 397浏览 收藏
-
- 文章 · php教程 | 9小时前 | php 数组稀疏化
- PHP稀疏数组处理技巧与实现方法
- 341浏览 收藏
-
- 文章 · php教程 | 9小时前 |
- PHP定时任务设置:crontab调用教程
- 128浏览 收藏
-
- 文章 · php教程 | 9小时前 | php连接mssql教程
- PHP连接MSSQL大数据处理优化方法
- 167浏览 收藏
-
- 文章 · php教程 | 9小时前 |
- PHP组件下载与源码获取攻略
- 366浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3587次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3824次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3798次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4948次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 4165次使用
-
- PHP技术的高薪回报与发展前景
- 2023-10-08 501浏览
-
- 基于 PHP 的商场优惠券系统开发中的常见问题解决方案
- 2023-10-05 501浏览
-
- 如何使用PHP开发简单的在线支付功能
- 2023-09-27 501浏览
-
- PHP消息队列开发指南:实现分布式缓存刷新器
- 2023-09-30 501浏览
-
- 如何在PHP微服务中实现分布式任务分配和调度
- 2023-10-04 501浏览

