PHP 8.4 Property Hooks — Complete Guide to Computed Property Accessors
In this tutorial, you will learn about PHP 8.4 Property Hooks. We cover key concepts, practical examples, and best practices to help you master this topic.
PHP 8.4 property hooks add get and set accessors to properties, enabling computed values, validation on assignment, Lazy Loading, and asymmetric visibility without separate getter and setter methods.
What You'll Learn
By the end of this tutorial, you'll implement get and set property hooks, create virtual properties, enforce validation on assignment, and use asymmetric visibility for better Encapsulation.
Why Property Hooks Matter
Property hooks replace boilerplate getter/setter methods with declarative syntax, making value computation and validation inline with property declarations.
Real-World Use
A User entity uses a property hook to compute the full name from first and last name, validates email format on assignment, and exposes a virtual property for age calculated from birth date.
Property Hooks Path
flowchart LR
A[JSON Validation] --> B[Property Hooks]
B --> C[enums/fibers]
B --> D[Readonly Classes]
C --> E[PSR Standards]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Get Hooks
Get hooks compute a value when the property is read.
<?php
class User {
public private(set) string $firstName;
public private(set) string $lastName;
public string $fullName {
get => "{$this->firstName} {$this->lastName}";
}
public function __construct(string $firstName, string $lastName) {
$this->firstName = $firstName;
$this->lastName = $lastName;
}
}
$user = new User("Alice", "Johnson");
echo $user->fullName;
Set Hooks with Validation
Set hooks validate or transform values during assignment.
<?php
class Email {
public string $value {
set(string $value) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException("Invalid email address");
}
$this->value = strtolower($value);
}
}
public function __construct(string $value) {
$this->value = $value;
}
}
$email = new Email("Alice@Example.com");
echo $email->value;
Asymmetric Visibility
Read and write visibility can differ for the same property.
<?php
class BankAccount {
public private(set) float $balance = 0.0;
public string $accountNumber {
get => $this->accountNumber;
set => throw new \LogicException("Account number cannot be changed");
}
public function __construct(
private string $accountNumber,
float $initialBalance = 0.0,
) {
$this->balance = $initialBalance;
}
public function deposit(float $amount): void {
if ($amount <= 0) throw new \InvalidArgumentException("Amount must be positive");
$this->balance += $amount;
}
}
Virtual Properties
Virtual properties have no backing store and are computed entirely from other data.
<?php
class Rectangle {
public function __construct(
public float $width,
public float $height,
) {}
public float $area {
get => $this->width * $this->height;
}
public float $perimeter {
get => 2 * ($this->width + $this->height);
}
public string $type {
get => $this->width === $this->height ? "Square" : "Rectangle";
}
}
$rect = new Rectangle(5, 10);
echo "Area: {$rect->area}, Type: {$rect->type}";
Lazy Loading with Property Hooks
Lazy-load expensive properties only when accessed.
<?php
class ExpensiveResource {
private ?array $data = null;
public array $processedData {
get {
if ($this->data === null) {
$this->data = $this->loadData();
}
return $this->data;
}
}
private function loadData(): array {
sleep(1);
return ["result" => "expensive computation"];
}
}
Common Mistakes
1. Infinite Recursion in Get Hooks
Accessing $this->property in a get hook for the same property causes infinite recursion.
2. Set Hook Without Type Enforcement
Set hooks can accept values that do not match the declared type. Validate type explicitly.
3. Overusing Virtual Properties
Virtual properties are convenient but can hide expensive computations. Consider eager loading for predictable performance.
4. Forgetting Asymmetric Visibility on Constructor
Constructor can set private(set) properties even though external code cannot.
5. Mixing Property Hooks with __get/__set Magic Methods
Properties with hooks bypass __get and __set. Use one pattern consistently.
Practice Questions
1. What is a get hook?
A get hook defines custom logic that runs when a property value is read.
2. What is asymmetric visibility?
Different visibility for reading (public) and writing (private(set)). Useful for read-only public properties.
3. What is a virtual property?
A property with a get hook but no backing field. The value is computed each time.
4. How do property hooks compare to __get and __set?
Hooks are declarative per-property. __get/__set are catch-all magic methods that handle undefined properties.
5. Challenge: Create a Temperature class with property hooks for conversion.
<?php
class Temperature {
public float $celsius {
set(float $value) {
if ($value < -273.15) throw new \InvalidArgumentException("Below absolute zero");
$this->celsius = $value;
}
}
public float $fahrenheit {
get => $this->celsius * 9 / 5 + 32;
set(float $value) {
$this->celsius = ($value - 32) * 5 / 9;
}
}
public function __construct(float $celsius = 0) {
$this->celsius = $celsius;
}
}
FAQ
Mini Project: Validated Model Base Class
Build a base model class with property hooks for automatic validation.
<?php
abstract class ValidatedModel {
protected array $errors = [];
protected function validate(string $field, mixed $value, array $rules): void {
foreach ($rules as $rule) {
if ($rule === "required" && empty($value)) {
$this->errors[$field][] = "{$field} is required";
}
if (str_starts_with($rule, "max:") && is_string($value)) {
$max = (int) substr($rule, 4);
if (strlen($value) > $max) $this->errors[$field][] = "{$field} exceeds {$max} characters";
}
}
}
public function isValid(): bool {
return empty($this->errors);
}
public function getErrors(): array {
return $this->errors;
}
}
What's Next
PHP PSR Standards PHP 8.1 Enums PHP 8.2 Readonly Classes
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro