介绍
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钩子没有类型声明,但属性本身具有字符串类型声明:
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
In the code above, we've defined a fullName property that has both a get and set hook. The get hook returns the full name by concatenating the first and last name together. The set hook splits the full name into the first and last name and sets them on their respective properties.
You may have also noticed that we're not setting a value on the fullName property itself. Instead, if we need to read the value of the fullName property, the get hook will be called to build up the full name from the first and last name properties. I've done this to highlight that you can have a property that doesn't have a value set directly on it, but instead, the value is calculated from other properties.
Using Property Hooks on Promoted Properties
A cool feature of property hooks is that you can also use them with constructor promoted properties.
Let's check out an example of a class that isn't using promoted properties and then look at what it might look like using promoted properties.
Our User class might look like so:
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; } }
We could promote the firstName and lastName properties into the constructor and define their set logic directly on the property:
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); } ) { // } }
Write-only Hooked Properties
If you define a hooked property with a setter that doesn't actually set a value on the property, then the property will be write-only. This means you can't read the value of the property, you can only set it.
Let's take our User class from the previous example and modify the fullName property to be write-only by removing the get hook:
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!
If we were to run the code above, we'd see the following error being thrown when attempting to access the fullName property:
Fatal error: Uncaught Error: Property User::$fullName is write-only
Read-only Hooked Properties
Similarly, a property can be read-only.
For example, imagine we only ever want the fullName property to be generated from the firstName and lastName properties. We don't want to allow the fullName property to be set directly. We can achieve this by removing the set hook from the fullName property:
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! } }
If we were to try and run the code above, the following error would be thrown because we're trying to set the fullName property directly:
Uncaught Error: Property User::$fullName is read-only
Using the "readonly" keyword
You can still make our PHP classes readonly even if they have hooked properties. For example, we may want to make the User class readonly:
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; } }
However, a hooked property cannot use the readonly keyword directly. For example, this class would be invalid:
class User { public readonly string $fullName { get => $this->firstName.' '.$this->lastName; } public function __construct( string $firstName, string $lastName, ) { $this->firstName = $firstName; $this->lastName = $lastName; } }
The above code would throw the following error:
Fatal error: Hooked properties cannot be readonly
The "PROPERTY" Magic Constant
In PHP 8.4, a new magic constant called __PROPERTY__ has been introduced. This constant can be used to reference the property name within the property hook.
Let's take a look at an example:
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; } }
In the code above, we can see that using __PROPERTY__ inside the lastName property's setter will output the property name lastName. However, it's also worth noting that trying to use this constant in an attempt to set the property value will trigger an error:
Fatal error: Uncaught Error: Must not write to virtual property User::$lastName
There's a handy use case example for the __PROPERTY__ magic constant that you can check out on GitHub: https://github.com/Crell/php-rfcs/blob/master/property-hooks/examples.md.
Hooked Properties in Interfaces
PHP 8.4 also allows you to define publicly accessible hooked properties in interfaces. This can be useful if you want to enforce that a class implements certain properties with hooks.
Let's take a look at an example interface with hooked properties declared:
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; } }
In the interface above, we're defining that any classes implementing the Nameable interface must have:
- A fullName property that is at least publicly gettable. This can be achieved by defining a get hook or not defining a hook at all.
- A firstName property that is at least publicly gettable.
- A lastName property that is at least publicly settable. This can be achieved by defining a property which has a set hook or not defining a hook at all. But if the class is read-only then the property must have a set hook.
This class that implements the Nameable interface would be valid:
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; } }
The class above would be valid because the fullName property has a get hook to match the interface definition. The firstName property only has a set hook, but is still publicly accessible so it satisfies the criteria. The lastName property doesn't have a get hook, but it is publicly settable so it satisfies the criteria.
Let's update our User class to enforce a get and set hook for the fullName property:
interface Nameable { public string $fullName { get; set; } public string $firstName { get; } public string $lastName { set; } }
Our User class would no longer satisfy the criteria for the fullName property because it doesn't have a set hook defined. It would cause the following error to be thrown:
Fatal error: Class User contains 1 abstract methods and must therefore be declared abstract or implement the remaining methods (Nameable::$fullName::set)
Hooked Properties in Abstract Classes
Similar to interfaces, you can also define hooked properties in abstract classes. This can be useful if you want to provide a base class that defines hooked properties that child classes must implement. You can also define the hooks in the abstract class and have them be overridden in the child classes.
For example, let's make a Model abstract class that defines a name property that must be implemented by child classes:
abstract class Model { abstract public string $fullName { get => $this->firstName.' '.$this->lastName; set; } abstract public string $firstName { get; } abstract public string $lastName { set; } }
In the abstract class above, we're defining that any classes that extend the Model class must have:
- A fullName property that is at least publicly gettable and settable. This can be achieved by defining a get and set hook or not defining a hook at all. We've also defined the get hook for the fullName property in the abstract class so we don't need to define it in the child classes, but it can be overridden if needed.
- A firstName property that is at least publicly gettable. This can be achieved by defining a get hook or not defining a hook at all.
- A lastName property that is at least publicly settable. This can be achieved by defining a property which has a set hook or not defining a hook at all. But if the class is read-only then the property must have a set hook.
We could then create a User class that extends the Model class:
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; } }
Conclusion
Hopefully, this article should have given you an insight into how PHP 8.4 property hooks work and how you might be able to use them in your PHP projects.
I wouldn't worry too much if this feature seems a little confusing at first. When I first saw it, I was a little confused too (especially with how they work with interfaces and abstract classes). But once you start tinkering with them, you'll soon get the hang of it.
I'm excited to see how this feature will be used in the wild and I'm looking forward to using it in my projects when PHP 8.4 is released.
If you enjoyed reading this post, you might be interested in checking out my 220+ page ebook "Battle Ready Laravel" which covers similar topics in more depth.
Or, you might want to check out my other 440+ page ebook "Consuming APIs in Laravel" which teaches you how to use Laravel to consume APIs from other services.
If you're interested in getting updated each time I publish a new post, feel free to sign up for my newsletter.
Keep on building awesome stuff! ?
以上是PHP 属性挂钩的详细内容。更多信息请关注PHP中文网其他相关文章!

PHP在现代Web开发中仍然重要,尤其在内容管理和电子商务平台。1)PHP拥有丰富的生态系统和强大框架支持,如Laravel和Symfony。2)性能优化可通过OPcache和Nginx实现。3)PHP8.0引入JIT编译器,提升性能。4)云原生应用通过Docker和Kubernetes部署,提高灵活性和可扩展性。

PHP适合web开发,特别是在快速开发和处理动态内容方面表现出色,但不擅长数据科学和企业级应用。与Python相比,PHP在web开发中更具优势,但在数据科学领域不如Python;与Java相比,PHP在企业级应用中表现较差,但在web开发中更灵活;与JavaScript相比,PHP在后端开发中更简洁,但在前端开发中不如JavaScript。

PHP和Python各有优势,适合不同场景。1.PHP适用于web开发,提供内置web服务器和丰富函数库。2.Python适合数据科学和机器学习,语法简洁且有强大标准库。选择时应根据项目需求决定。

PHP是一种广泛应用于服务器端的脚本语言,特别适合web开发。1.PHP可以嵌入HTML,处理HTTP请求和响应,支持多种数据库。2.PHP用于生成动态网页内容,处理表单数据,访问数据库等,具有强大的社区支持和开源资源。3.PHP是解释型语言,执行过程包括词法分析、语法分析、编译和执行。4.PHP可以与MySQL结合用于用户注册系统等高级应用。5.调试PHP时,可使用error_reporting()和var_dump()等函数。6.优化PHP代码可通过缓存机制、优化数据库查询和使用内置函数。7

PHP成为许多网站首选技术栈的原因包括其易用性、强大社区支持和广泛应用。1)易于学习和使用,适合初学者。2)拥有庞大的开发者社区,资源丰富。3)广泛应用于WordPress、Drupal等平台。4)与Web服务器紧密集成,简化开发部署。

PHP在现代编程中仍然是一个强大且广泛使用的工具,尤其在web开发领域。1)PHP易用且与数据库集成无缝,是许多开发者的首选。2)它支持动态内容生成和面向对象编程,适合快速创建和维护网站。3)PHP的性能可以通过缓存和优化数据库查询来提升,其广泛的社区和丰富生态系统使其在当今技术栈中仍具重要地位。

在PHP中,弱引用是通过WeakReference类实现的,不会阻止垃圾回收器回收对象。弱引用适用于缓存系统和事件监听器等场景,需注意其不能保证对象存活,且垃圾回收可能延迟。

\_\_invoke方法允许对象像函数一样被调用。1.定义\_\_invoke方法使对象可被调用。2.使用$obj(...)语法时,PHP会执行\_\_invoke方法。3.适用于日志记录和计算器等场景,提高代码灵活性和可读性。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

VSCode Windows 64位 下载
微软推出的免费、功能强大的一款IDE编辑器

SublimeText3 Linux新版
SublimeText3 Linux最新版

ZendStudio 13.5.1 Mac
功能强大的PHP集成开发环境

SublimeText3 英文版
推荐:为Win版本,支持代码提示!

Atom编辑器mac版下载
最流行的的开源编辑器