Do Readonly Properties replace Getters (in PHP)?

Doğan Uçar
6 min readFeb 11, 2023

--

I am in programming since around 2006 and started coding with Borland C++ (I can hear you saying “Bor -what”). Back that time, we created Desktop UI applications with object oriented C++ and together with the language, we learned best practices and common approaches (to object oriented programming).

One major rule of thumb — among others — is: make members private and create getters and setters for each. This ensures that object state can not be changed from outside and the behaviour is controllable and predictable.

OO concept followers advocate this as best practice and considered everything else as “pain in the stomach” for a long time. And to be honest, I belong to this group since I think that object overriding opens door and barn for messy code. Non private attributes does not only allow to change the state from outside, there is also the possibility to update attribute values from classes that inherite. I addressed the missconcept of Traits which is a bad compromise to bypass the limitation of the single inheritance principal in a previous post. See here if you are interested.

The Concept of Immutability

Software Engineering is indeed an interesting profession. While many things change rapidly, others stay since a long time and new concepts establish slowly or get popular late. One of the interesting concepts — which exists for decades — is using immutable objects. The idea is very simple: Data Transfer Objects (DTO), Entities or Value Objects (VO) are created once and does not have the ability to change their state. This means in practice: once a new class is created, (usually) values are provided by passing them through the constructor and disallowing public/protected/private setters.

class Car {

private Engine $engine;
private int $doors;

public function __construct(Engine $engine, int $doors) {
$this->engine = $engine;
$this->doors = $doors;
}

public function getEngine(): Engine {
return $this->engine;
}

public function getDoors(): int {
return $this->doors;
}

}

As you can see in the concept above, the Car class gets its attributes injected into the constructor and only provides public getters. There is no possibility to change the state of a Car instance once it is instantiated. This brings many advantages:

  • Liability: very simple, but powerful. Creating a new instance means that there is new new memory is allocated. By simply doing a strict compare (===) it can be determined whether a change happened or not.
  • Object History: Different parts of the application or even a third party API can do any changes on the object. By having an object for every change, there is a history of every change. Further, it is very easy to say whether there is a change without comparing all attributes and their values by just using strict compare.
  • Being Reactive with Changes: Some applications are very costly in terms of memory usage, CPU and/or real money. Implemented immutable objects correctly, strict compare can be breaked down to each attribute and react only when data has changed.
  • Testability/Single Source of Truth: Being as less stateless as possible improves testability a lot. This is usually due to it’s nature of being stateless or immutability.

Readonly Properties (in PHP)

One of the changing “new” things in software engineering — at least for PHP and JavaScript based frameworks — is the support of the programming language to constructs like readonly properties and even classes. Java and Objective-C had these concepts earlier and PHP introduced it with the version 8.1.

Readonly properties are properties of an object that can only be read and cannot modified once they are set. In the past, readonly properties were created “by convention”, as shown in the example below:

class Foo {
private final int $prop;

public function __construct(int $prop) {
$this->prop = $prop;
}

public function getProp():int {
return $this->prop;
}
}

As shown in the example, the attribute is final and can only set while instantiating. By making a property readonly, you can prevent accidental changes to its value and enforce the intended usage of the property.

With version 8.1, PHP introduced readonly properties “officially”: the “readonly” keyword can be used to declare a readonly property. The keyword makes the property non-modifiable, even within the class where it is defined.

class Foo {

public function __construct(private readonly int $prop) {
}

public function getProp():int {
return $this->prop;
}
}

As you can see in the example, the property is defined once in the constructor and is readonly during its lifetime (even for the class itself). Apart of that, the boilerplate code for the class itself shrinked as there is no attribute declaration and no setter needed.

The problem with getters

Taking the example of the readonly declaration above, we could even shrink the class declaration more by declaring the property public and removing the getter. In the past, this was considered as bad practice in the world of object oriented programming. However, we can argue that there is no property change possible once the object is created and therefore, there is no need to make a dedicated getter for it, as advocated in this blog post.

Well, that is true. On the other hand, keep in mind that this was possible with the “by convention” way as well and people did not do it (at least those advocating private properties in OO). Another thing is the problem with interfaces that occure. For instance, for Keestash, we love to work with public entities and private implementation. This means, that we have a public blueprint of a class (for instance the IUser interface, which defines (getter) methods that we think a user must provide) and a private concrete implementation that implements the methods of the interface (Some consider this as bad practice since interfaces should describe business logic behaviour (e.g. for services) and not being a shape for an entity object). This provides a safe way to give a structure to the public where the concrete implementation can be changed as long as it complies with the interface. But however, interfaces allow the definition of methods only. There is no way to force concrete classes to implement given attributes. This way, the idea of blueprinting classes will shrink down to “just” implementing an empty interface.

And by the way: if you are looking for an open source password manager, Keestash is the perfect solution for you and your team. Keestash is open source, meaning that everyone can download and install it. For teams and enterprises, we provide a SaaS version and — among others — several enterprise features such as integrations and custom extensions.

https://keestash.com

Conclusion

My very personal opinion on this topic is to mix up both approaches. While readonly properties guarantee that properties can not be changed later, public getters enable to create blueprint interfaces for entity classes. Combined with interfaces for services, we can use these interfaces to describe a behaviour, the results and the overall interaction of an application without having a concrete implementation.

The readonly-ness of the attributes guarantee that there will be no changes and throws an exception in the worst case. This will pop up very early in development and can be avoided.

Therefore, I follow the approach of creating public interfaces with getters for concrete classes that implement readonly properties. This way I promise myself to benefit from the advantages of both concepts with minimizing the disadvantages. There is a post of Brent to extend interfaces to implement properties as well. Who knows, maybe this ends up in a RFC and gets implemented in a upcoming PHP version. But as far as this is not possible, I consider the approach mentioned above as best practice.

On the other hand, there is a major downside of this approach. For instance, consider a controller that updates data of an entity to persist in the database. Since the objects are readonly, we need to create a new object, copy all properties over and make the change we want. This does not end up only in boilerplate code, but also — depending on the situation — memory issues (to be honest, this should not be a problem for modern systems but for smaller instances with edge cases like creating objects in a loop).

But what do you think about readonly properties, getters and setters and immutable objects? Let me know, that would of course interest me very much.

This post was originally posted on my personal blog. I created a list of pro and contras for readonly properties vs getters/setters. Please subscribe to my newsletter on my blog to get the list for free 😊

--

--

Doğan Uçar
Doğan Uçar

Written by Doğan Uçar

Software Engineer, PHP/Laminas (Zend), Backend, Cloud, Freelancer & CEO, Open Source Contributor