Asymmetric visibility for PHP properties
One of the lesser visible feature coming to PHP 8.4, pun intended, is the assymetric visibility for PHP properties. It is an upgrade of the current visibility, and also, a number of refined features over readonly, properties hooks or magic methods. It is not for everyone, but it might be quite useful. Let’s take a look!
Asymmetric Visibility
Personnally, I didn’t know that visibility was symmetric. How can public
, protected
and private
have some symmetry? It actually boils down to read and write. A property is both private
for reading and writing. That’s all.
<?php class Square { public int $side; public int $area; function __construct(int $side) { $this->side = $side; $this->area = $side * $side; } function setSide(int $side) { $this->side = $side; $this->area = $side * $side; } } $square = new Square(4); echo $square->side; // 4 echo $square->area; // 16 ?>
Here, the public property acts as a rustic getter/setter (or lack of). It makes sens for $side
, but it is more awkward for the $area
property, which should actually be readable from anywhere, but writeable only with $side
.
For PHP 8.4, Ilija Tovilo and Larry Garfield, are now introducing a new visibility setting for writing. Literally, a set
visibility: private(set)
.
<?php class Square { public int $side; public private(set) int $area; function __construct(int $side) { $this->side = $side; $this->area = $side * $side; // Allowed } } $square = new Square(4); echo $square->side; // 4 echo $square->area; // 16 $square->area = 15; // Forbidden! ?>
With this new setting, the $area
property may be read with public
visibility: a.k.a., everywhere. And it can only set with private
visibility: a.k.a. from inside the class. Here, it is the constructor. Et voila!
The read and write visibilities are now asymmetric, as they do not have the same scope of application.
Which visibilities for which properties?
With now 2 sets of visibilities, PHP is introducing no less than 9 different configurations of visibilities. This is quite a lot, in fact, and it seems it is going to make the visibility declaration harder to read. But, don’t get scared, there are some classic user-friendliness down the road.
So, let’s see if we can get a better understanding of all these visibilities.
The three symmetric visibilities
First, let’s see the symmetric visibilities. After all, private private(set)
, protected protected(set)
and public public(set)
are the symmetric visibilities. We have had them ever since the conception of PHP (or almost… sorry Rasmus), and we already have a nice short hand to write them : private
, protected
, public
. So, no change for these: keep them short and backward compatible!
Also, the new token private(set)
(and its cousins), are an addition to the language. This means they can be used alone, without the classic read visibilities. And, what happens when the visibility is omitted? It is by default public
. This behavior also stays in PHP 8.4, even when setting explicitely the asymmetric visibility.
<?php class x { private(set) int $p; // No explicit public visibility function __construct() { $this->p = 3; } } $x = new x; echo $x->p; // 3 $x->p = 2; // Cannot modify private(set) property x::$p from global scope ?>
The three asymmetric visibilities
The three asymmetric visibilities are the following : public private(set)
, protected private(set)
and public protected(set)
. These are not symmetric (see above), and they provide a distinct scope for reading (public
), and one for writing (private(set)
. This is the example we had with the initial Square
class.
The three illegal visibilities
The last three asymmetric visibilities are the following : private public(set)
, private protected(set)
and protected public(set)
. These are the last, where the host class cannot write its properties, but can get them written from everywhere in the application. All these make little sense, and are forbidden by PHP.
<?php class x { private public(set) int $p; // No explicit public visibility function __construct() { $this->p = 3; } } //Fatal error: Visibility of property x::$p must not be weaker than set visibility ?>
For those of you who have used writeable-only files on the system, the comparison is not straightforward. While writing in a file where one can’t read is a valid usage on the OS (think, logs, which are read elsewhere), this is not applicable for an object. It would be easy to circumvent this limitation and access the property anyway.
Syntax summary
private | protected | public | |||
---|---|---|---|---|---|
private(set) | Sym. | Illegal | Illegal | ||
protected(set) | Useful | Sym. | Illegal | ||
public(set) | Useful | Useful | Sym. |
A few precautions
Asymmetry also for static properties
Static properties also support asymmetric visibility.
<?php class x { static public private(set) string $p = 'a'; static function toggleP() { x::$p = 'abc'; } } echo x::$p; // OK a x::toggleP(); // Update echo x::$p; // abc x::$p = 'ab'; // Fatal error: forbidden ?>
Note that the RFC mentions that static properties would not be covered, but a few manual tests showed that it is not the case.
Only for typed properties
Asymmetric visibility is only for typed properties. It’s another compile-time syntax error.
<?php class x { private private(set) $p = 2; } // Property with asymmetric visibility x::$p must have type ?>
The PHP 8.3 error message
Obviously, this new syntax is not backward compatible. This means that, once you have adopted the asymmetric visibilities, it is not possible to go back to PHP 8.3 or older. The error message is actually intriguing:
<?php class x { private private(set) int $p = 2; } // On PHP 8.3 // Multiple access type modifiers are not allowed ?>
Yes, PHP doesn’t allow the multiplication of private private private $property
and tell you about it. This check was introduced in PHP 5.3: then, you could make properties really really private by repeating the keyword. That ‘feature’ was removed in modern versions of PHP, for the better of everyone.
private(set), case insensitive and no space
The final funny aspect of this new token is its syntax constraints. It is a PHP keyword, and as such, is case insensitive. Hugh, UPPERCASE KEYWORDS…
On the other hand, it doesn’t tolerate any space anywhere, or it yields a PHP 8.3 error message. Nothing difficult to remember. Yet, when one is accustomed to the white-space flexibility of the append operator, it is difficult not to regret it.
<?php class x { // OK private PRIVATE(SET) int $p = 2; // KO private PRIVATE( SET) int $p2 = 3; // Multiple access type modifiers are not allowed } $a[] = 1; $a [] = 1; $a [ ]= 1; $a []= 1; $a [ /* yeah */ ] = 1; ?>
A RFC worth reading
There is a lot more to mention about the asymmetric visibility, in particular its relationship with references, objects, and arrays, and readonly, and, obviously, with the upcoming property hooks, and inheritance, and sentinel data, etc.
This is too long to write here, and also, the RFC is extremely well written and detailled. Take a look at it, and then, thank Ilija and Larry for the hard work they put in this new PHP gem.
Until then, happy auditing!
In the mean time, give a try to the new asymmetric visibility for PHP properties and its new syntax with your own download of the PHP 8.4, for testing purposes, or online with 3v4l.org (select git.master as PHP 8.4 version).