Skip to content
  • Homepage
  • HTML
  • CSS
  • Symfony
  • PHP
  • How to
  • Contact
  • Donate

Teach Developer

Articles, Guides & Tips

PHP 8.1: read-only properties

Home  »  Top Tutorials   »   PHP 8.1: read-only properties
Posted on September 25, 2022September 25, 2022
705

Writing data transfer objects and value objects in PHP have become significantly easier over the years. Take for example a look at a DTO in PHP 5.6:

class BlogData
{
    /** @var string */
    private $title;
    
    /** @var Status */
    private $status;
    
    /** @var \DateTimeImmutable|null */
    private $publishedAt;
   
   /**
    * @param string $title 
    * @param Status $status 
    * @param \DateTimeImmutable|null $publishedAt 
    */
    public function __construct(
        $title,
        $status,
        $publishedAt = null
    ) {
        $this->title = $title;
        $this->status = $status;
        $this->publishedAt = $publishedAt;
    }
    
    /**
     * @return string 
     */
    public function getTitle()
    {
        return $this->title;    
    }
    
    /**
     * @return Status 
     */
    public function getStatus() 
    {
        return $this->status;    
    }
    
    /**
     * @return \DateTimeImmutable|null 
     */
    public function getPublishedAt() 
    {
        return $this->publishedAt;    
    }
}

And compare it to its PHP 8.0’s equivalent:

class BlogData
{
    public function __construct(
        private string $title,
        private Status $status,
        private ?DateTimeImmutable $publishedAt = null,
    ) {}
    
    public function getTitle(): string
    {
        return $this->title;    
    }
    
    public function getStatus(): Status 
    {
        return $this->status;    
    }
    
    public function getPublishedAt(): ?DateTimeImmutable
    {
        return $this->publishedAt;    
    }
}

That’s already quite the difference, though I think there’s still one big issue: all those getters. Personally, I don’t use them anymore since PHP 8.0 with its promoted properties. I simply prefer to use public properties instead of adding getters:

class BlogData
{
    public function __construct(
        public string $title,
        public Status $status,
        public ?DateTimeImmutable $publishedAt = null,
    ) {}
}

Object-oriented purists don’t like this approach though: an object’s internal status shouldn’t be exposed directly, and definitely not be changeable from the outside.

In our projects at Spatie, we have an internal style guide rule that DTOs and VOs with public properties shouldn’t be changed from the outside; a practice that seems to work fairly well, we’ve been doing it for quite some time now without running into any problems.

However, yes; I agree that it would be better if the language ensured that public properties couldn’t be overwritten at all.

class BlogData
{
    public function __construct(
        public readonly string $title,
        public readonly Status $status,
        public readonly ?DateTimeImmutable $publishedAt = null,
    ) {}
}

This keyword basically does what its name suggests: once a property is set, it cannot be overwritten anymore:

$blog = new BlogData(
    title: 'PHP 8.1: readonly properties', 
    status: Status::PUBLISHED, 
    publishedAt: now()
);

$blog->title = 'Another title';

Error: Cannot modify readonly property BlogData::$title

Knowing that, when an object is constructed, it won’t change anymore, gives a level of certainty and peace when writing code: a whole range of unforeseen data changes simply can’t happen anymore.

Of course, you still want to be able to copy data over to a new object, and maybe change some properties along the way. We’ll discuss how to do that with read-only properties later in this post. First, let’s look at them in depth.

Only typed properties

Readonly properties can only be used in combination with typed properties:

class BlogData
{
    public readonly string $title;
    
    public readonly $mixed;
}

You can however use mixed as a type hint:

class BlogData
{
    public readonly string $title;
    
    public readonly mixed $mixed;
}

The reason for this restriction is that by omitting a property type, PHP will automatically set a property’s value to null if no explicit value was supplied in the constructor. This behavior, combined with read-only, would cause unnecessary confusion.

Both normal and promoted properties

You’ve already seen examples of both: readonly can be added both on normal, as well as promoted properties:

class BlogData
{
    public readonly string $title;
    
    public function __construct(
        public readonly Status $status, 
    ) {}
}

No default value

Readonly properties can not have a default value:

class BlogData
{
    public readonly string $title = 'Readonly properties';
}

That is unless they are promoted properties:

class BlogData
{
    public function __construct(
        public readonly string $title = 'Readonly properties', 
    ) {}
}

The reason that it is allowed for promoted properties, is because the default value of a promoted property isn’t used as the default value for the class property, but only for the constructor argument. Under the hood, the above code would transpile to this:

class BlogData
{
    public readonly string $title;
    
    public function __construct(
        string $title = 'Readonly properties', 
    ) {
        $this->title = $title;
    }
}

You can see how the actual property doesn’t get assigned a default value. The reason for not allowing default values on read-only properties, by the way, is that they wouldn’t be any different from constants in that form.

Inheritance

You’re not allowed to change the read-only flag during inheritance:

class Foo
{
    public readonly int $prop;
}

class Bar extends Foo
{
    public int $prop;
}

This rule goes in both directions: you’re not allowed to add or remove the readonly flag during inheritance.

Unset is not allowed

Once a read-only property is set, you cannot change it, not even unset it:

$foo = new Foo('value');

unset($foo->prop);

Reflection

There’s a new ReflectionProperty::isReadOnly() method, as well as a ReflectionProperty::IS_READONLY flag.

Cloning

So, if you can’t change read-only properties, and if you can’t unset them, how can you create a copy of your DTOs or VOs and change some of their data? You can’t clone them, because you wouldn’t be able to overwrite their values. There’s actually an idea to add a clone with construct in the future that allows this behavior, but that doesn’t solve our problem now.

Well, you can copy over objects with changed read-only properties, if you rely on a little bit of reflection magic. By creating an object without calling its constructor (which is possible using reflection), and then by manually copying each property over — sometimes overwriting its value — you can in fact “clone” an object and change its read-only properties.

Top Tutorials

Post navigation

Previous Post: PHP 8: Attributes
Next Post: TOP 10 MOST POPULAR PROGRAMMING LANGUAGES IN 2022

Related Posts

  • How to use events listeners and Event Subscriber in Symfony
  • What’s new in PHP 8.2
  • How to Deploy a React application on a cPanel
  • PHP 8: Attributes
  • Dealing with deprecations
  • What is SSH in Linux?

Categories

  • Codeigniter (3)
  • CSS (11)
  • eCommerce (1)
  • Framework (1)
  • Git (3)
  • How to (43)
  • HTML (5)
  • JavaScript (15)
  • Jquery (7)
  • Laravel (1)
  • Linux (4)
  • Magento-2 (1)
  • Node js (4)
  • Others (2)
  • PHP (11)
  • React (13)
  • Server (1)
  • SSH (3)
  • Symfony (6)
  • Tips (16)
  • Top Tutorials (10)
  • Ubuntu (3)
  • Vue (1)
  • Wordpress (7)

Latest Posts

  • What is SSH in Linux?
  • How to Delete Files in Ubuntu Command Line
  • How to Deploy a React application on a cPanel
  • How to use events listeners and Event Subscriber in Symfony
  • How to Convert PHP CSV to JSON

WEEKLY TAGS

AJAX (1) Codeigniter (1) Javascript (11) JQuery (1) PHP (16) Programming (1) React (3) Symfony (1)

Random Post

How to fix getFullyear() is not a function with JavaScript?
Difference between var, let, and const in JavaScript
How to check the PHP version
How to Make Toggles With React Hooks
Important most things you should know about JSX

Quick Navigation

  • About
  • Contact
  • Privacy Policy

© Teach Developer 2021. All rights reserved