Let PHP callers read state without granting write access
PHP 8.4 separates property read and write visibility. Use it to expose state clearly while keeping state transitions behind methods that express their purpose.
An application can let callers see a value without letting them replace it. PHP 8.4 made that distinction expressible directly in property visibility. For a business application, the useful consequence is a clearer boundary around changes to state: a screen can display an object's value while the object retains responsibility for changing it through an intentional operation.
Looking back from November 2026, this is a design lesson from the November general release. It is separate from the earlier discussion of property hooks during the alpha cycle. Hooks concern behaviour attached to property access. Asymmetric visibility concerns which scope can perform that access, especially writes. One feature should not be used as a vague explanation of the other.
Establish the release date separately from the feature
PHP's 2024 news archive records the 21 November release announcement under PHP 8.4.1. The 8.4.1 changelog entry also carries 21 November 2024. These dated records establish the historical milestone; the number on the first general release should not be silently rewritten to fit an expectation that it must end in zero.
The PHP 8.4 feature page explains asymmetric visibility and demonstrates public private(set). It has no displayed publication date, so it supports the feature explanation rather than independently dating the launch. In that example, callers can read a property while writes remain inside its declaring class. A public method can still perform an internal update.
That small distinction can remove a getter whose only purpose is returning a stored value. It does not remove the need to decide which changes the application should permit. Syntax makes a selected boundary concise; the team still chooses the boundary.
Model a change as a decision
Consider an illustrative order object with a status displayed in several screens. Letting every caller assign arbitrary status strings makes it difficult to locate the rules for moving an order forward. Restricting direct writes can bring changes back into the object. An operation such as approving an order can then express the intended transition and check its preconditions.
This example proposes a design, not a tested implementation or an AL+ client system. The important choice is where an accepted change happens. A public readable property can make observation straightforward while a method makes the operation discoverable. Reviewers can examine the method instead of searching for unrelated assignments scattered through the application.
There is a business consequence to that discoverability. When someone asks why an order moved to an approved state, the code should lead to a named action with defined conditions. A general assignment communicates only a new value. It does not communicate whether the caller checked the rule that made the transition legitimate.
Visibility alone cannot define those conditions. An internal method that assigns any requested value without checking it can preserve the same weak business rule behind a tighter language boundary. The improvement requires both a restricted write path and an operation with the appropriate meaning.
Read visibility has its own cost
Exposing a property publicly tells callers that reading it is part of the object's interface. That is a commitment worth making deliberately. A value that is easy to display today may later need a different representation. Existing consumers then become part of the migration, even though they were never allowed to write.
For a small data object, that commitment can be reasonable. For a domain object whose internal representation is likely to change, a method may preserve a clearer separation between storage and the information callers need. Reducing boilerplate is useful only when it does not obscure the longer-lived interface decision.
Object visibility also differs from user authorization. private(set) describes a PHP access boundary. It does not establish whether the signed-in employee is allowed to approve an order or whether the request belongs to the right account. The application must make that decision before carrying out the operation. Moving a write into a method cannot substitute for checking who asked for it.
The same caution applies to HTTP output. A publicly readable PHP property is not an instruction to expose the value to every external client. Response construction and data permissions remain application concerns. Keep the language-level convenience from silently broadening the product's information policy.
Migrate a small interface before a class hierarchy
For an existing application, start with a property whose readers and writers can be identified. List direct writes, construction paths and any framework code that fills objects from stored data. Tightening visibility can reveal callers that relied on assignment. Those callers need an intentional replacement rather than a mechanical syntax change.
Do not combine the runtime upgrade, property-interface changes and a broad domain redesign into one unexplained task. They carry different risks. Establish compatibility with the supported PHP version first, then make a small boundary change whose callers can be inspected. The acceptance question is whether the permitted business operations still work and forbidden writes are excluded in the intended places.
These are proposed migration checks, not commands executed for this article. Libraries, serialization paths and framework conventions need assessment in the project concerned. A release announcement cannot establish compatibility for an application it never examined.
A method can still be the better interface
The strongest counterargument is that behaviour-rich objects should expose meaningful operations rather than property-shaped APIs. That is persuasive when reading or changing state has domain significance. A method can communicate purpose and leave room for a representation change. Asymmetric visibility does not make every getter obsolete or every public value desirable.
Use the feature where the value is intentionally observable and direct mutation should be restricted. Retain methods where the operation itself deserves a name. In custom software development, ask the team to identify one state change whose responsibility is currently scattered. Put that responsibility in a clear operation, then choose the simplest read interface that the application can maintain.
Paul