What Type of Properties Can `readonly` Be Applied to in PHP 8.1?
PHP

What Type of Properties Can `readonly` Be Applied to in PHP 8.1?

Symfony Certification Exam

Expert Author

October 1, 20236 min read
PHPSymfonyPHP 8.1Readonly PropertiesWeb DevelopmentSymfony Certification

What Type of Properties Can readonly Be Applied to in PHP 8.1?

The release of PHP 8.1 has introduced numerous features that enhance the language's capabilities, one of the most notable being the readonly property. For developers, particularly those working within the Symfony framework, understanding how and where to apply readonly properties is crucial for writing robust, maintainable code. This article will delve into the types of properties that can utilize readonly, their implications, and practical examples tailored for Symfony applications.

Understanding readonly Properties

The readonly property in PHP allows developers to define properties that can only be written once, either during declaration or within the constructor. This feature is particularly useful for creating immutable objects, which are frequently utilized in domain-driven design (DDD) and functional programming paradigms.

Why Use readonly Properties?

In Symfony development, immutability enhances reliability and predictability. By using readonly properties, developers can:

  • Prevent Unintended Modifications: Once a property is set, it cannot be changed, reducing potential bugs from unexpected state changes.
  • Improve Thread Safety: Immutable objects are inherently thread-safe, which can be beneficial in high-concurrency environments.
  • Simplify Debugging: With fixed property values, tracking down state-related bugs becomes easier.

Types of Properties That Can Be readonly

1. Simple Properties

The most straightforward use case for readonly properties is simple scalar values. These can be strings, integers, booleans, or floats defined at the class level.

class User
{
    public readonly string $username;

    public function __construct(string $username)
    {
        $this->username = $username;
    }
}

$user = new User('john_doe');
echo $user->username; // Outputs: john_doe
// $user->username = 'jane_doe'; // Fatal error: Cannot modify readonly property

In this example, the username property is set during object construction and cannot be modified afterward. This pattern is common in Symfony entities where user data is often immutable.

2. Properties with Default Values

readonly properties can also have default values. This is useful for creating objects with predefined states.

class Product
{
    public readonly string $name = 'Default Product';

    public function __construct(public readonly float $price)
    {
    }
}

$product = new Product(19.99);
echo $product->name; // Outputs: Default Product

Here, the name property has a default value, while price is assigned at construction. This pattern allows for cleaner code in scenarios where certain values are often the same.

3. Properties in Value Objects

In Symfony, value objects frequently use immutability to represent concepts like monetary values, dates, and more. Using readonly properties aligns perfectly with this pattern.

class Money
{
    public function __construct(
        public readonly int $amount,
        public readonly string $currency
    ) {
    }

    public function format(): string
    {
        return number_format($this->amount / 100, 2) . ' ' . strtoupper($this->currency);
    }
}

$money = new Money(1500, 'usd');
echo $money->format(); // Outputs: 15.00 USD

In this case, both amount and currency are set upon construction and cannot be altered, ensuring that the monetary value remains consistent throughout the object's lifecycle.

4. Properties in Data Transfer Objects (DTOs)

When dealing with data transfer objects (DTOs) in Symfony, readonly properties can be extremely beneficial for ensuring that data remains consistent once received from a request.

class UserDTO
{
    public function __construct(
        public readonly string $email,
        public readonly string $name
    ) {
    }
}

$userDto = new UserDTO('[email protected]', 'John Doe');
echo $userDto->email; // Outputs: [email protected]
// $userDto->email = '[email protected]'; // Fatal error

DTOs often serve as the bridge between the controller and the domain model in Symfony applications, making immutability a valuable trait.

5. Properties in Symfony Form Models

In Symfony forms, readonly properties can be used to create models that should not change after they are set. This is especially relevant when you want to ensure that form submissions do not alter sensitive data.

class RegistrationFormModel
{
    public function __construct(
        public readonly string $username,
        public readonly string $email
    ) {
    }
}

$model = new RegistrationFormModel('john_doe', '[email protected]');
echo $model->username; // Outputs: john_doe
// $model->username = 'jane_doe'; // Fatal error

6. Properties in Services

readonly properties can also be applied to services, ensuring that configuration values set during instantiation remain constant.

class ApiService
{
    public function __construct(
        public readonly string $apiKey,
        public readonly string $baseUri
    ) {
    }

    public function callApi(string $endpoint): array
    {
        // Use $this->apiKey and $this->baseUri
    }
}

$apiService = new ApiService('my-secret-api-key', 'https://api.example.com');
echo $apiService->baseUri; // Outputs: https://api.example.com
// $apiService->baseUri = 'https://api.newexample.com'; // Fatal error

Using readonly for service properties ensures that the configuration remains unchanged after the service is constructed, aligning with best practices for service management in Symfony.

Practical Examples in Symfony Applications

Example 1: Using readonly in Doctrine Entities

When creating Doctrine entities, you can leverage readonly properties to ensure that certain fields are immutable after creation.

use DoctrineORMMapping as ORM;

#[ORMEntity]
class Order
{
    #[ORMId]
    #[ORMGeneratedValue]
    public readonly int $id;

    public function __construct(
        public readonly string $orderNumber,
        public readonly DateTimeInterface $createdAt
    ) {
    }
}

// Usage
$order = new Order('ORD-001', new DateTime());
echo $order->orderNumber; // Outputs: ORD-001

In this example, the id is automatically generated and readonly, while orderNumber and createdAt are set during construction.

Example 2: Symfony Form Handling

In a Symfony form, you can create a form model with readonly properties to prevent any modification of values after the form is processed.

class ArticleFormModel
{
    public function __construct(
        public readonly string $title,
        public readonly string $content
    ) {
    }
}

// Controller
$formModel = new ArticleFormModel('New Article', 'Content of the article');

// After form submission, these properties cannot be changed
echo $formModel->title; // Outputs: New Article

Example 3: Value Object for Email

Creating a value object for email addresses as readonly can help ensure that the email is treated consistently throughout the application.

class Email
{
    public function __construct(public readonly string $email)
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("Invalid email address");
        }
    }
}

$email = new Email('[email protected]');
echo $email->email; // Outputs: [email protected]
// $email->email = '[email protected]'; // Fatal error

This pattern is especially important in contexts like user registration or email notifications, where maintaining the integrity of the email address is critical.

Conclusion

Understanding the types of properties that can utilize the readonly keyword in PHP 8.1 is essential for Symfony developers. By applying readonly properties, developers can create immutable objects that enhance the reliability and maintainability of their applications. Whether in entities, DTOs, or service classes, readonly properties streamline code and prevent unintended modifications, which is vital in a framework as robust as Symfony.

As you prepare for the Symfony certification exam, consider how these principles apply to your projects. Practice implementing readonly properties in your Symfony applications, and leverage their benefits to write cleaner, more maintainable code. Embracing immutability through readonly will not only improve your coding practices but also align your work with modern PHP standards.