PHP 8.4 wird im November 2024 veröffentlicht und wird eine coole neue Funktion mit sich bringen: Property Hooks.
In diesem Artikel werfen wir einen Blick darauf, was Eigenschafts-Hooks sind und wie Sie sie in Ihren PHP 8.4-Projekten verwenden können.
Als Randbemerkung könnte es für Sie auch interessant sein, meinen anderen Artikel zu lesen, der Ihnen die neuen Array-Funktionen zeigt, die in PHP 8.4 hinzugefügt werden.
Eigenschafts-Hooks ermöglichen es Ihnen, benutzerdefinierte Getter- und Setter-Logik für Klasseneigenschaften zu definieren, ohne separate Getter- und Setter-Methoden schreiben zu müssen. Dies bedeutet, dass Sie die Logik direkt in der Eigenschaftsdeklaration definieren können, sodass Sie direkt auf eine Eigenschaft (wie $user->firstName) zugreifen können, ohne daran denken zu müssen, eine Methode aufzurufen (wie $user->getFirstName() und $user- >setFirstName()).
Sie können sich den RFC für diese Funktion unter https://wiki.php.net/rfc/property-hooks ansehen
Wenn Sie ein Laravel-Entwickler sind und diesen Artikel lesen, werden Sie möglicherweise feststellen, dass die Hooks den Accessoren und Mutatoren in Laravel-Modellen sehr ähnlich sehen.
Mir gefällt das Aussehen der Property-Hooks-Funktion sehr gut und ich kann mir vorstellen, dass ich sie in meinen Projekten verwenden werde, wenn PHP 8.4 veröffentlicht wird.
Um zu verstehen, wie Eigenschafts-Hooks funktionieren, werfen wir einen Blick auf einige Beispielverwendungen.
Sie können einen Get-Hook definieren, der immer dann aufgerufen wird, wenn Sie versuchen, auf eine Eigenschaft zuzugreifen.
Stellen Sie sich zum Beispiel vor, Sie haben eine einfache User-Klasse, die im Konstruktor einen Vornamen und einen Nachnamen akzeptiert. Möglicherweise möchten Sie eine fullName-Eigenschaft definieren, die den Vor- und Nachnamen miteinander verkettet. Dazu könnten Sie einen Get-Hook für die Eigenschaft fullName definieren:
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
Im obigen Beispiel können wir sehen, dass wir einen Get-Hook für die Eigenschaft „fullName“ definiert haben, der einen Wert zurückgibt, der durch Verketten der Eigenschaften „firstName“ und „lastName“ berechnet wird. Wir können dies noch etwas aufräumen, indem wir eine Syntax verwenden, die auch Pfeilfunktionen ähnelt:
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
Es ist wichtig zu beachten, dass der vom Getter zurückgegebene Wert mit dem Typ der Eigenschaft kompatibel sein muss.
Wenn strikte Typen nicht aktiviert sind, wird der Wert typabhängig an den Eigenschaftstyp angepasst. Wenn Sie beispielsweise eine Ganzzahl von einer Eigenschaft zurückgeben, die als Zeichenfolge deklariert ist, wird die Ganzzahl in eine Zeichenfolge konvertiert:
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"
Obwohl wir im obigen Beispiel 123 als zurückzugebende Ganzzahl angegeben haben, wird „123“ als Zeichenfolge zurückgegeben, da die Eigenschaft eine Zeichenfolge ist.
Wir können „declare(strict_types=1);“ hinzufügen; an den Anfang des Codes, um eine strenge Typprüfung zu ermöglichen:
declare(strict_types=1); class User { public string $fullName { get { return 123; } } public function __construct( public readonly string $firstName, public readonly string $lastName, ) { // } }
Dies würde nun dazu führen, dass ein Fehler ausgegeben wird, da der Rückgabewert eine Ganzzahl ist, die Eigenschaft jedoch eine Zeichenfolge:
Fatal error: Uncaught TypeError: User::$fullName::get(): Return value must be of type string, int returned
PHP 8.4-Eigenschafts-Hooks ermöglichen Ihnen auch die Definition eines Set-Hooks. Dies wird immer dann aufgerufen, wenn Sie versuchen, eine Eigenschaft festzulegen.
Sie können zwischen zwei separaten Syntaxen für den Set-Hook wählen:
Sehen wir uns beide Ansätze an. Wir stellen uns vor, dass wir die ersten Buchstaben des Vor- und Nachnamens in Großbuchstaben schreiben möchten, wenn sie in der Benutzerklasse festgelegt werden:
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
Wie wir im obigen Beispiel sehen können, haben wir einen Set-Hook für die Eigenschaft „firstName“ definiert, der den ersten Buchstaben des Namens in Großbuchstaben schreibt, bevor er für die Eigenschaft festgelegt wird. Wir haben außerdem einen Set-Hook für die Eigenschaft „lastName“ definiert, der eine Pfeilfunktion verwendet, um den Wert zurückzugeben, der für die Eigenschaft festgelegt werden soll.
Wenn die Eigenschaft eine Typdeklaration hat, muss auch für ihren Set-Hook ein kompatibler Typ festgelegt sein. Das folgende Beispiel gibt einen Fehler zurück, da der Set-Hook für firstName keine Typdeklaration hat, die Eigenschaft selbst jedoch eine Typdeklaration vom Typ string:
hat
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; } }
Der Versuch, den obigen Code auszuführen, würde dazu führen, dass der folgende Fehler ausgegeben wird:
Fatal error: Type of parameter $name of hook User::$firstName::set must be compatible with property type
Sie sind nicht darauf beschränkt, die Get- und Set-Hooks separat zu verwenden. Sie können sie zusammen in derselben Immobilie nutzen.
Nehmen wir ein einfaches Beispiel. Wir stellen uns vor, wir hätten eine fullName-Eigenschaft in unserer User-Klasse. Wenn wir die Eigenschaft festlegen, teilen wir den vollständigen Namen in den Vor- und Nachnamen auf. Ich weiß, dass dies ein naiver Ansatz ist und es viel bessere Lösungen gibt, aber es dient lediglich der Veranschaulichung, um die Hakeneigenschaften hervorzuheben.
Der Code könnte etwa so aussehen:
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.
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); } ) { // } }
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
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
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
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.
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:
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)
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:
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; } }
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! ?
Das obige ist der detaillierte Inhalt vonPHP-Eigenschafts-Hooks. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!