compiler icon indicating copy to clipboard operation
compiler copied to clipboard

Implement property hooks via virtual properties

Open thekid opened this issue 1 year ago • 6 comments

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 use debug_backtrace() for this!~~ addressed in ed5003f44f6de3b3c3db7e152a1c40392b7fcfb6
  • ~~The syntax for invoking parent selectors parent::$x::get() is not implemented yet to be rewritten to parent::__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

thekid avatar May 13 '23 20:05 thekid