Currently it is not possible to prevent 'accidental' mutation of object properties.
Considers this code, where a PR has been made to add a setPath method to a class:
class Settings {
protected string $path;
public function __construct(string $path)
{
check_path_is_acceptable($path);
// Do not change path after this point!
$this->path = $path;
}
// 400 lines of code here
// 400 lines of code here
// 400 lines of code here
// Team member wants to add this function
// And as the PR is many lines below the note saying
// do not change, the reviewer might not notice the problem
public function setPath(string $path)
{
$this->path = $path; //
}
}
It would
class Settings {
protected immutable string $path;
public function __construct(string $path)
{
check_path_is_acceptable($path);
// Do not change path after this point!
$this->path = $path;
}
// Team member wants to add this function
// And as the PR is many lines below the note saying
// do not change, the reviewer might not notice the problem
public function setPath(string $path)
{
$this->path = $path;
// This generates an error saying the value of path
// cannot be modified after being set.
}
}
The main reason the RFC failed before was due to a lack of support modiyfing properties during the cloning process of an object, which would have greatly restricted people's abilities to use immutable properties.
Sometimes during the clone of an object, you need to modify at least one property of the object. The new value can either be generated internally, or externally from the class.
When the new value of the property is generated internally to the clone method, this probably requires 'unlocking' the properties to be mutable again, during the clone of that object.
class Foo
{
private immutable $id;
public function __construct()
{
$this->id = generate_unique_id();
}
public function __clone()
{
// This doesn't error, as object properties are 'unlocked'
// from being immutable during clone.
$this->id = generate_unique_id();
}
}
class Foo
{
private immutable $id;
public function __construct($id)
{
$this->id = $id;
}
public function withNewId($newId) {
$clone = clone $this;
// This wouldn't work, we'd need a better solution
$clone->id = $newId;
return $clone;
}
}
class Foo
{
private immutable $id;
public function __construct($id)
{
$this->id = $id;
}
public function withNewId($newId) {
$clone = clone($this, id: $newId);
// This wouldn't work, we'd need a better solution
$clone->id = $newId;
return $clone;
}
}
tbd this apparently is an issue.
tbd this apparently is an issue.
There is at least some overlap between this idea, and 'property accesores' aka public get, private set
types of things, which have been raised before. However it's reasonably likely imo that i) the immutable idea is the more important of the two ii) it's also the more likely to have a reasonable discussion and implementation.
https://wiki.php.net/rfc/propertygetsetsyntax-v1.2 https://wiki.php.net/rfc/propertygetsetsyntax
Looking at the C# feature, init-only might be the best one, as it accurately describes when the property can be changed.
It's almost certainly the case that it would be appropriate for immutable/init-only properties to be changeable via Reflection, as this is both a useful thing to do, and consistent with other parts of the language where reflection can work around limits in the language.
Likely to pass when the above hurdles are addressed.
Two previous RFCs.
https://wiki.php.net/rfc/object-initializer https://wiki.php.net/rfc/write_once_properties