PHP 8.4: A New Era for Developers
The much-anticipated release of PHP 8.4 is scheduled for November 21, 2024, and it comes with an array of exciting features aimed at enhancing the capabilities of PHP developers. Among these new additions is the concept of asymmetric visibility, which, when used in conjunction with property hooks, is set to transform the way developers design data and message objects in PHP. Let’s delve into the details of PHP 8.4 and explore how asymmetric visibility can be implemented effectively.
Understanding PHP Visibility
The concept of visibility in PHP was first introduced in PHP 5, providing developers with a mechanism to control access to class members. Visibility in PHP can be classified into three types:
- Public: This visibility level allows access from anywhere, whether within the class instance itself or from any other part of the code where the instance is available.
- Protected: Access is restricted to within the class itself and any subclasses that extend from it.
- Private: Access is strictly confined to the class where the property or method is defined, preventing access from subclasses.
Here’s a practical example to illustrate these visibility levels:
php<br /> enum LogLevel: string<br /> {<br /> case INFO = 'info';<br /> case NOTICE = 'notice';<br /> case WARNING = 'warning';<br /> case ERROR = 'error';<br /> case CRITICAL = 'critical';<br /> }<br /> <br /> abstract class AbstractMessage<br /> {<br /> private const TEMPLATE = '[%s] (%s) %s';<br /> <br /> abstract public function __toString(): string;<br /> <br /> protected function formatMessage(string $message, LogLevel $level): string<br /> {<br /> return sprintf(<br /> self::TEMPLATE, <br /> (new DateTimeImmutable())->format('c'),<br /> $level->value,<br /> $message<br /> );<br /> }<br /> }<br /> <br /> class DefaultMessage extends AbstractMessage<br /> {<br /> public function __construct(<br /> private string $message,<br /> private LogLevel $level,<br /> ) {<br /> }<br /> <br /> public function __toString(): string<br /> {<br /> return $this->formatMessage($this->message, $this->level);<br /> }<br /> }<br /> <br /> class JsonMessage extends DefaultMessage<br /> {<br /> protected function formatMessage(string $message, LogLevel $level): string<br /> {<br /> return json_encode([<br /> 'message' => $message,<br /> 'severity' => $level->value,<br /> 'ts' => (new DateTimeImmutable())->format('c'),<br /> ]);<br /> }<br /> }<br /> <br /> $message = new DefaultMessage('A basic message', LogLevel::INFO);<br /> $jsonMessage = new JsonMessage('A JSON message', LogLevel::NOTICE);<br /> echo $message;<br /> echo $jsonMessage;<br />
In this example, the constant
TEMPLATE
is private, meaning it can only be accessed withinAbstractMessage
. TheformatMessage()
method is protected, allowing it to be accessed within the class and its subclasses. The properties$message
and$level
are private, accessible only withinDefaultMessage
. However,JsonMessage
can still use these properties indirectly through methods defined inDefaultMessage
.The Problem Solved by Asymmetric Visibility
Asymmetric visibility addresses a significant challenge: ensuring that objects representing messages or data remain valid and immutable after creation. In traditional designs, developers might face issues such as:
- Ensuring a non-empty message.
- Guaranteeing that severity and timestamp are set.
- Preventing modification of properties representing specific states after instantiation.
PHP 8.1 introduced readonly properties, which provide a way to enforce immutability, but they have limitations, especially when it comes to validation and inheritance. Asymmetric visibility offers a solution by allowing different levels of access for reading and writing properties.
Introducing Asymmetric Visibility
The concept of asymmetric visibility, or "aviz," permits properties to have separate visibility levels for read and write operations. This feature is defined using specific syntax:
php<br /> {read visibility} {set visibility}(set) {propertyType} $name;<br />
Key points to remember:
- If no read visibility is specified, it defaults to public.
- Write visibility is denoted by the term "(set)" and must be less or equally visible as the read visibility.
- Asymmetric visibility necessitates a type declaration for the property.
Here’s how you can use asymmetric visibility:
php<br /> // Publicly accessible, writable internally only<br /> public private(set) string $message;<br /> <br /> // Publicly accessible, writable from instance and extending classes<br /> public protected(set) string $message;<br /> <br /> // Accessible within instance and extensions, writable only within defining instance<br /> protected private(set) string $message;<br />
Implementing Asymmetric Visibility
To demonstrate the practical use of asymmetric visibility, consider refactoring the
Message
class:php<br /> final class Message<br /> {<br /> public private(set) string $message {<br /> set (string $value) {<br /> if (preg_match('/^\s*$/', $value)) {<br /> throw new InvalidArgumentException('message must be non-empty');<br /> }<br /> $this->message = $value;<br /> }<br /> }<br /> <br /> public function __construct(<br /> string $message,<br /> public readonly LogLevel $severity,<br /> public readonly DateTimeInterface $timestamp,<br /> ) {<br /> $this->message = $message;<br /> }<br /> }<br />
By adding
private(set)
to the$message
property, we ensure it cannot be altered from outside the class, while still allowing internal modifications. Making the classfinal
and excluding additional methods achieves full immutability.A Developer’s Guide to Asymmetric Visibility
When implementing asymmetric visibility, developers need to consider several interactions and contexts:
References
Access to a reference requires set visibility, meaning the reference’s visibility must match the set visibility. For example:
php<br /> class Access<br /> {<br /> public protected(set) int $counter = 0;<br /> }<br /> <br /> // Invalid reference access<br /> $counter = &$accessInstance->counter;<br /> <br /> // Valid method incrementing the counter<br /> class Access<br /> {<br /> public protected(set) int $counter = 0;<br /> <br /> public function increment(): void<br /> {<br /> $this->counter++;<br /> }<br /> }<br />
Arrays
Arrays pose challenges with asymmetric visibility due to implicit reference access when writing to them. For instance:
php<br /> class Collection<br /> {<br /> public private(set) array $items = [];<br /> }<br /> <br /> // Invalid array append<br /> $collection = new Collection();<br /> $collection->items[] = new Item('value');<br />
To manipulate arrays, provide a method within the class:
php<br /> class Collection<br /> {<br /> public private(set) array $items = [];<br /> <br /> public function insert(Item $item): void<br /> {<br /> $this->items[] = $item;<br /> }<br /> }<br />
Inheritance and Interfaces
Asymmetric visibility behaves similarly to standard visibility in inheritance. Properties must maintain the same or higher visibility in derived classes. However,
private(set)
is treated as final, preventing redeclaration in subclasses.PHP readonly Properties
The
readonly
flag introduced in PHP 8.1 functions similarly toprivate(set)
but with a write-once rule. With asymmetric visibility, readonly properties can now be seen asprotected(set)
, streamlining the engine’s behavior.Property Overloading
PHP supports property overloading using magic methods like
__get()
,__set()
, etc. Asymmetric visibility allows developers to avoid these methods by providing more direct control over property access.Reflection
To accommodate asymmetric visibility, the ReflectionProperty class includes new methods:
isProtectedSet()
andisPrivateSet()
. These methods help determine the set visibility of properties, ensuring comprehensive access controls.Final Thoughts
The introduction of asymmetric visibility, alongside property hooks, empowers PHP developers to create robust, immutable data objects and messages. Prior to PHP 8.1, developers had to employ complex workarounds for property management, often leading to convoluted code. The readonly flag in PHP 8.1 offered a step forward, but with limitations regarding inheritance.
With property hooks, developers gain the ability to validate property values and define virtual properties, while asymmetric visibility restricts when and how properties can be modified. This combination offers a powerful, native solution for previously challenging scenarios.
As PHP 8.4 rolls out, it’s exciting to anticipate how developers will leverage these features to innovate and enhance the language’s capabilities. Whether you’re planning an upgrade or exploring new projects, understanding and utilizing asymmetric visibility will undoubtedly be a valuable asset in your PHP development toolkit.
For more Information, Refer to this article.