Support the ongoing development of Laravel.io →
Article Hero Image

Asymmetric Property Visibility in PHP

13 Jun, 2025 9 min read

Photo by Casey Horner on Unsplash

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:

[GETTER_VISIBILITY] [SETTER_VISIBILITY(set)] [TYPE] $propertyName

Where:

  • GETTER_VISIBILITY - can be public, protected, or private.
  • SETTER_VISIBILITY(set) - can be public, protected, or private.
  • 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:

protected 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:

class Article
{
    public function __construct(
        public protected(set) string $title,
        public private(set) string $author,
        protected private(set) string $content,
    ) {}
}

In this example, we've defined that:

  • title has public getter visibility and protected setter visibility.
  • author has public getter visibility and private setter visibility.
  • content has protected getter visibility and private 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:

$article = new Article(
    title: 'Battle Ready Laravel',
    author: 'Ash Allen',
    content: 'Article content here',
);

// ✅ We can access the title and author publicly.
echo $article->title; // Battle Ready Laravel
echo $article->author; // Ash Allen

// ❌ We can't read the content property publicly, as it has protected visibility.
// This will throw an error.
echo $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:

Fatal 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:

class Article
{
    public function __construct(
        public protected(set) string $title,
        public private(set) string $author,
        protected private(set) string $content,
    ) {}
    
    public function getContent(): string
    {
        return $this->content;
    }
}

Now we would be able to fetch the content of the article using the getContent method:

$article = new Article(
    title: 'Battle Ready Laravel',
    author: 'Ash Allen',
    content: 'Article content here',
);

echo $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:

$article = new Article(
    title: 'Battle Ready Laravel',
    author: 'Ash Allen',
    content: 'Article content here',
);

// ❌ We cannot set the "content" value publicly, as it has protected visibility.
$article->content = 'New article content here';

Attempting to run the above code will throw an error with the following message:

Fatal 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:

class Article
{
    public function __construct(
        public protected(set) string $title,
        public private(set) string $author,
        protected private(set) string $content,
    ) {}
    
    public function setContent(string $content): void
    {
        $this->content = $content;
    }
}

We would then be able to use this method like so:

$article = new Article(
    title: 'Battle Ready Laravel',
    author: 'Ash Allen',
    content: 'Article content here',
);

$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:

class Article
{
    public function __construct(
        public protected(set) string $title,
        public private(set) string $author,
        protected private(set) string $content,
    ) {}
}

We can rewrite the property definitions like so:

class Article
{
    public function __construct(
        protected(set) string $title,
        private(set) string $author,
        protected private(set) string $content,
    ) {}
}

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:

class Article
{
    public function __construct(
        protected(set) string $title,
        private(set) $author,
        protected private(set) string $content,
    ) {}
}

This would result in the following error:

Fatal 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:

class Article
{
    public function __construct(
        protected(set) string $title,
        private protected(set) $author,
        protected private(set) string $content,
    ) {}
}

This would result in the following error:

Fatal 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:

class Article
{
    public function __construct(
        public string $title,
        private(set) string$author,
        protected private(set) string $content,
    ) {}
}

class ChildArticle extends Article
{
    private string $author;
    
    // ...
}

$article = new ChildArticle(
    title: 'Battle Ready Laravel',
    author: 'Ash Allen',
    content: 'Article content here',
);

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:

Fatal 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.

Keep on building awesome stuff! 🚀

Last updated 1 day ago.

driesvints liked this article

1
Like this article? Let the author know and give them a clap!
ash-jc-allen (Ash Allen) I'm a freelance Laravel web developer from Preston, UK. I maintain the Ash Allen Design blog and get to work on loads of cool and exciting projects 🚀

Other articles you might like

Article Hero Image June 13th 2025

Formatting Monetary Values in JavaScript

Introduction When building your web applications, you might need to format numbers as monetary value...

Read article
Article Hero Image June 12th 2025

Check Every Key Exists in a PHP Array with Arr::hasAll()

Introduction In Laravel 12.16, a new hasAll method was added to the Illuminate\Support\Arr class. Th...

Read article
Article Hero Image June 10th 2025

Cast Laravel Model Fields to "Illuminate\Support\Uri" with "AsUri"

Introduction I recently contributed a new feature to the Laravel framework that allows you to cast m...

Read article

We'd like to thank these amazing companies for supporting us

Your logo here?

Laravel.io

The Laravel portal for problem solving, knowledge sharing and community building.

© 2025 Laravel.io - All rights reserved.