compiler
compiler copied to clipboard
Implement property hooks via virtual properties
This pull request adds support for property hooks to all PHP versions
Example
This example calculates and caches the full name from first and last names
class User {
private string $full;
private string $first { set { $field= $value; unset($this->full); } }
private string $last { set { $field= $value; unset($this->full); } }
public string $fullName => $this->full??= $this->first.' '.$this->last;
public function __construct(string $first, string $last) {
$this->first= $first;
$this->last= $last;
}
}
$u= new User('Timm', 'Friebe');
$u->fullName; // "Timm Friebe"
Implementation
The above results in the following being emitted:
class User {
private string $full;
private function __set_first($value) { $this->__virtual['first']= $value; unset($this->full); }
private function __set_last($value) { $this->__virtual['last']= $value; unset($this->full); }
public function __get_fullName() { return $this->full??= $this->first.' '.$this->last; }
public function __construct(string $first, string $last) {
$this->first= $first;
$this->last= $last;
}
private $__virtual= ['first' => null, 'last' => null, 'fullName' => null];
public function __set($name, $value) {
switch ($name) {
case 'first': /* Check omitted for brevity */ $this->__set_first($value); break;
case 'last': /* Check omitted for brevity */ $this->__set_last($value); break;
default: throw new \Error('Unknown property '.$name);
}
}
public function __get($name) {
switch ($name) {
case 'fullName': return $this->__get_fullName(); break;
default: throw new \Error('Unknown property '.$name);
}
}
}
- Although the code could be inlined within
__set
and__get
, exceptions raised would print incorrect line numbers in their stack trace! - The check for private and protected properties uses
debug_backtrace()
to verify the calling scope.
Shortcomings
- ~~Currently, property modifiers are unchecked, the
__set
and__get
functions effectively make them public, we would need to usedebug_backtrace()
for this!~~ addressed in ed5003f44f6de3b3c3db7e152a1c40392b7fcfb6 - ~~The syntax for invoking parent selectors
parent::$x::get()
is not implemented yet to be rewritten toparent::__get_x()
as the former is ambiguous, see RFC~~ addressed in f21a615a6143667b21006630943026b7c3e124e1, see comment below - We always create a backing property as seen in the above case for fullName, where it wouldn't be necessary.
- Overwriting properties with properties with hooks will not work correctly due to the nature of this implementation with
__set
/__get
, see https://github.com/xp-framework/compiler/pull/166#issuecomment-2002075374
See also
- https://wiki.php.net/rfc/property-hooks
- https://github.com/Crell/php-rfcs/blob/master/property-hooks/examples.md
- https://github.com/xp-framework/ast/pull/45
- https://github.com/iluuu1994/php-src/pull/82
- https://github.com/php/php-src/pull/13455
- https://www.swiftbysundell.com/articles/property-wrappers-in-swift/
- https://github.com/xp-framework/compiler/issues/167
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get