Introduction
Asymmetric visibility is a feature that was introduced in PHP 8.4 (released: November 2024) and allows you to define different visibility levels for reading and writing properties. This is a feature which I've not seen used much in the wild yet, but I think will become more common as developers become more familiar with it.
In this article, we're going to explore asymmetric visibility in PHP and how to use it. By the end of the article, you should feel confident enough to give it a try in your own applications.
What is Asymmetric Visibility in PHP?
You can use PHP 8.4's asymmetric visibility feature to define different visibility levels for the getter and setter of a property. For example, you might want to allow a property to be publicly readable, but only settable within the class or a subclass. This is useful for encapsulating the state of an object while still allowing it to be read from outside the class.
As a side note, if you don't want the value to be changed at all after it's been set, you might want to reach for "readonly" properties instead.
Asymmetric property visibility is defined using the following syntax:
1[GETTER_VISIBILITY] [SETTER_VISIBILITY(set)] [TYPE] $propertyName
Where:
-
GETTER_VISIBILITY
- can bepublic
,protected
, orprivate
. -
SETTER_VISIBILITY(set)
- can bepublic
,protected
, orprivate
. -
TYPE
- the type of the property (e.g.,string
,int
,array
, etc.).
For example, to define a string property with protected
getter visibility and private
setter visibility, you would write:
1protected private(set) string $propertyName;
In this instance, it means that the property can be read by the class or a subclass extending the class, but can only be set within the class itself.
How to Use Asymmetric Visibility in PHP
To fully understand how to use asymmetric visibility in PHP, let's look at an example class that uses it.
We'll create a simple Article
class with three properties: title
, author
, and content
. Each property will have different visibility levels for the getter and setter methods.
The Article
class will look like this:
1class Article2{3 public function __construct(4 public protected(set) string $title,5 public private(set) string $author,6 protected private(set) string $content,7 ) {}8}
In this example, we've defined that:
-
title
haspublic
getter visibility andprotected
setter visibility. -
author
haspublic
getter visibility andprivate
setter visibility. -
content
hasprotected
getter visibility andprivate
setter visibility.
Let's now take our Article
class and look at how we can read and write to the properties with asymmetric visibility.
Reading Asymmetric Visibility Properties
We'll start by looking at how we can read the properties with asymmetric visibility. To do this, we'll create an instance of the Article
class and then try to access the properties:
1$article = new Article( 2 title: 'Battle Ready Laravel', 3 author: 'Ash Allen', 4 content: 'Article content here', 5); 6 7// โ
We can access the title and author publicly. 8echo $article->title; // Battle Ready Laravel 9echo $article->author; // Ash Allen10 11// โ We can't read the content property publicly, as it has protected visibility.12// This will throw an error.13echo $article->content;
As we can see in the example above, we can access the title
and author
properties publicly. However, we cannot access the content
property from outside the class because it has protected
visibility (meaning we can only read it from inside the class or a subclass). Attempting to access the content
property will throw an error with the following message:
1Fatal error: Uncaught Error: Cannot access protected property Article::$content in /in/5JIAK:24
In order to access the content
property, we would need to create a method within the Article
class (or a class extending the Article
class) that returns the value of the content
property. For example:
1class Article 2{ 3 public function __construct( 4 public protected(set) string $title, 5 public private(set) string $author, 6 protected private(set) string $content, 7 ) {} 8 9 public function getContent(): string10 {11 return $this->content;12 }13}
Now we would be able to fetch the content of the article using the getContent
method:
1$article = new Article(2 title: 'Battle Ready Laravel',3 author: 'Ash Allen',4 content: 'Article content here',5);6 7echo $article->getContent(); // Article content here
Writing to Asymmetric Visibility Properties
Sticking with our Article
class, let's look at how we can write to the properties which are using asymmetric visibility.
We've defined our content
property with protected private(set) string $content
which means it has a private setter. As a result, this means if we try and set the value of the content
property directly from outside the class, it will throw an error. For example:
1$article = new Article(2 title: 'Battle Ready Laravel',3 author: 'Ash Allen',4 content: 'Article content here',5);6 7// โ We cannot set the "content" value publicly, as it has protected visibility.8$article->content = 'New article content here';
Attempting to run the above code will throw an error with the following message:
1Fatal error: Uncaught Error: Cannot access protected property Article::$content in /in/EVU2P:22
Instead, in this scenario, we might want to create a setter method within the Article
class that allows us to set the value of the content
property. For example:
1class Article 2{ 3 public function __construct( 4 public protected(set) string $title, 5 public private(set) string $author, 6 protected private(set) string $content, 7 ) {} 8 9 public function setContent(string $content): void10 {11 $this->content = $content;12 }13}
We would then be able to use this method like so:
1$article = new Article(2 title: 'Battle Ready Laravel',3 author: 'Ash Allen',4 content: 'Article content here',5);6 7$article->setContent('New article content here');
Omitting the Public Getter
If a property is using public
getter visibility, you can omit the getter visibility and just declare the setter visibility. This is useful for properties that you want to be publicly readable but only settable within the class or a subclass.
For example, let's take this example class:
1class Article2{3 public function __construct(4 public protected(set) string $title,5 public private(set) string $author,6 protected private(set) string $content,7 ) {}8}
We can rewrite the property definitions like so:
1class Article2{3 public function __construct(4 protected(set) string $title,5 private(set) string $author,6 protected private(set) string $content,7 ) {}8}
In the example above, we've removed the public
getter visibility from the title
and author
properties because they're publicly readable. But we've needed to leave the protected
getter visibility in place for the content
property.
I'm not sure what the general consensus is on this. Should we always explicitly declare the getter visibility? Or should we omit it if it's public
? I'm sure as this feature gets more use, we'll decide on this as a community (or may already have done). For the time being, I like the idea of explicitly declaring the getter visibility, as it makes the code more readable for me. I can definitely see myself looking at a private(set) string $author
property and thinking "This property is completely private" at first glance, rather than "This property is publicly readable, but only settable within the class". But that's just my personal preference, and may likely change over time.
Caveats of Asymmetric Visibility in PHP
There are several caveats to be aware of when using asymmetric visibility in your PHP code:
Typed Properties Only
Only typed properties can be declared with asymmetric visibility. This means that you cannot use this feature with untyped properties.
For example, let's take an example class which declares an author
property with asymmetric visibility without a type:
1class Article2{3 public function __construct(4 protected(set) string $title,5 private(set) $author,6 protected private(set) string $content,7 ) {}8}
This would result in the following error:
1Fatal error: Property with asymmetric visibility Article::$author must have type in /in/vWagH on line 5
set
Visibility Must Be More Restrictive than get
Visibility
The setter's visibility must be the same as the getters or more restrictive. For example, you wouldn't be able to declare a property with protected public(set)
visibility, as this would mean the setter (public
) is more visible than the getter (protected
).
For instance, let's take this example class which declares an author
property with invalid asymmetric visibility:
1class Article2{3 public function __construct(4 protected(set) string $title,5 private protected(set) $author,6 protected private(set) string $content,7 ) {}8}
This would result in the following error:
1Fatal error: Visibility of property Article::$author must not be weaker than set visibility in /in/0vM44 on line 5
As a guide to help you remember this, here's a list of valid asymmetric visibility combinations:
-
public protected(set)
- Public getter, protected setter. -
public private(set)
- Public getter, private setter. -
protected private(set)
- Protected getter, private setter.
And here's a list of invalid asymmetric visibility combinations:
-
private protected(set)
- Private getter, protected setter. -
private public(set)
- Private getter, public setter. -
protected public(set)
- Protected getter, public setter.
Properties with private(set)
are Final
If a property is declared with a private
setter, then it will automatically be considered final
. This means it cannot be overridden in a child class.
For instance, let's take the following example where we want to override the author
property in a child class:
1class Article 2{ 3 public function __construct( 4 public string $title, 5 private(set) string$author, 6 protected private(set) string $content, 7 ) {} 8} 9 10class ChildArticle extends Article11{12 private string $author;13 14 // ...15}16 17$article = new ChildArticle(18 title: 'Battle Ready Laravel',19 author: 'Ash Allen',20 content: 'Article content here',21);
In the example above, we've tried to override the author
property in the ChildArticle
class. However, because the author
property has a private(set)
visibility, it is considered final
and will throw an error when we try to run the code:
1Fatal error: Cannot override final property Article::$author in /in/L6QXL on line 12
Related PHP 8.4 Articles
You might also be interested in reading some of my other articles that cover features introduced in PHP 8.4:
Conclusion
In this article, we've looked at asymmetric visibility in PHP and how to use it. We explored how to define properties with different visibility levels for reading and writing, and how to read and write to those properties.
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 below.
Keep on building awesome stuff! ๐