当前位置:首页 > 文章列表 > 文章 > php教程 > PHP 属性挂钩

PHP 属性挂钩

来源:dev.to 2024-08-25 19:15:50 0浏览 收藏

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

PHP 属性挂钩

介绍

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学习网公众号吧!

版本声明
本文转载于:dev.to 如有侵犯,请联系study_golang@163.com删除
使用住宅代理解决机器人流量挑战:识别、使用和检测指南使用住宅代理解决机器人流量挑战:识别、使用和检测指南
上一篇
使用住宅代理解决机器人流量挑战:识别、使用和检测指南
上海市消保委:厂商不能用权限对其他渠道软件进行不合理限制,消费者有知情权
下一篇
上海市消保委:厂商不能用权限对其他渠道软件进行不合理限制,消费者有知情权
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    508次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • 毕业宝AIGC检测:AI生成内容检测工具,助力学术诚信
    毕业宝AIGC检测
    毕业宝AIGC检测是“毕业宝”平台的AI生成内容检测工具,专为学术场景设计,帮助用户初步判断文本的原创性和AI参与度。通过与知网、维普数据库联动,提供全面检测结果,适用于学生、研究者、教育工作者及内容创作者。
    16次使用
  • AI Make Song:零门槛AI音乐创作平台,助你轻松制作个性化音乐
    AI Make Song
    AI Make Song是一款革命性的AI音乐生成平台,提供文本和歌词转音乐的双模式输入,支持多语言及商业友好版权体系。无论你是音乐爱好者、内容创作者还是广告从业者,都能在这里实现“用文字创造音乐”的梦想。平台已生成超百万首原创音乐,覆盖全球20个国家,用户满意度高达95%。
    26次使用
  • SongGenerator.io:零门槛AI音乐生成器,快速创作高质量音乐
    SongGenerator
    探索SongGenerator.io,零门槛、全免费的AI音乐生成器。无需注册,通过简单文本输入即可生成多风格音乐,适用于内容创作者、音乐爱好者和教育工作者。日均生成量超10万次,全球50国家用户信赖。
    24次使用
  •  BeArt AI换脸:免费在线工具,轻松实现照片、视频、GIF换脸
    BeArt AI换脸
    探索BeArt AI换脸工具,免费在线使用,无需下载软件,即可对照片、视频和GIF进行高质量换脸。体验快速、流畅、无水印的换脸效果,适用于娱乐创作、影视制作、广告营销等多种场景。
    26次使用
  • SEO标题协启动:AI驱动的智能对话与内容生成平台 - 提升创作效率
    协启动
    SEO摘要协启动(XieQiDong Chatbot)是由深圳协启动传媒有限公司运营的AI智能服务平台,提供多模型支持的对话服务、文档处理和图像生成工具,旨在提升用户内容创作与信息处理效率。平台支持订阅制付费,适合个人及企业用户,满足日常聊天、文案生成、学习辅助等需求。
    28次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码