No change necessary, can be overridden with hooks without changes.
class C {
public int $prop;
}
Gets called anytime the property is accessed. Changes to the property are forbidden, via reference or otherwise.
class C {
public int $prop {
get {
static $i = 0;
return $i++;
}
}
}
The property can be accessed and mutated, although not via reference or indirectly (i.e. through $c->prop['foo']['bar'] = 'bar';
). This is usually useful when wrapping a property and using it to store the actual value.
class C {
public array $accessedProperties = [];
public string $_prop;
public string $prop {
get {
$this->accessedProperties[] = 'prop';
return $_prop;
}
set {
// $value is guaranteed to be a string here
$this->_prop = strtoupper($value);
}
}
}
The property still stores its own value but calls the {before,after}Set
methods to avoid he need to create property wrappers for simple cases. The same caveat applies, the property must not contain references and can't be modified indirectly. beforeSet
allows transforming and returning a value before it is stored.
class C {
public string $prop {
beforeSet {
return strtoupper($value);
}
}
}
Sometimes a sub class wants to add behavior to a property, like modifying the value before it is stored, or adding validation. Overriding properties by adding hooks is allowed. The parent property can be accessed through parent::$prop
. This will either call the parent {get,set}
hook or access the native property, depending on what the parent has declared. The reference and indirect modification caveat once again applies. For this reason, this is not a good approach for arrays.
class C {
public string $prop;
}
class D extends C {
public string $prop {
beforeSet {
return strtoupper($value);
}
}
}
As mentioned previously, because hooks disallow indirect modification and references and that's the primary way to modify arrays it is not practical to create array properties with hooks. In general, we don't recommend making array properties publicly writable at all. The reason for that is that arrays can be modified in many ways with little control over it, as PHP does not support typed arrays. Instead, we believe that is is preferable to only make the array accessible in public but provide dedicated methods in the given class to make specific changes to the array. This requires a little more typing but avoids the need to do sanitation of C::$names
when used. Furthermore, overriding a modification of C::$names
becomes as easy as overriding the given modifier method.
class C {
/** @var list<string> $_names */
private array $_names;
/** @var list<string> $names */
public array $names { get => $this->_names; }
public function addName(string $name) {
$ths->_names[] = $name;
}
}
Some thoughts on this approach:
In the previous proposal, only properties that enabled hooks could be overridden. This was achieved through the public string $prop {}
syntax. The rationale was that adding hooks to a property would constitute a BC break because they disable references and indirect modification. However, there are a couple of downsides to this:
- Every property would need to modified with
{}
to make adding hooks possible, all to avoid BC breaks when adding the hooks due to disabling indirect modification and references. For non-array
properties this is mostly irrelevant as indirect modification is not possible and interaction with references is rare. - The difference between
public string $foo;
andpublic string $foo {}
is not immediately obvious and has proven confusing to many people. - It adds further complexity to both the language and implementation.
While this approach could be considered unsound by potentially breaking references and indirect modification in polymorphic code that assumes dealing with the parent class, we think that risk is not worth the benefits of adding it. Furthermore, there are two possible mitigation strategies that could be implemented:
- Error when creating an
array
-typed property with hooks- Advantage: Cheap and easy to implement
- Disadvantage: Will only catch properties that are typed with
array
, can be circumvented withmixed
- Throw when storing any array in a property with hooks to immediately hint at the fact that this might not work as you'd expect
- Advantage: Will work for any property with hooks, no matter what the type is
- Disadvantage: Only works at runtime and will come at a performance cost
Either way, this should be a rare occurrence. Non-array properties cannot be modified indirectly and rarely interact with references. For native array properties we don't recommend exposing them publicly in the first place.