-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reusable property hooks #167
Comments
Proof of concept implementationSee https://gist.github.com/thekid/dc12c4c4f4cf3f971b7dbbf4a5cd83b4 The property object holds the following:
DelegatesThese could be added to a class ByLazy {
public function __construct(private callable $init) { }
public function get($self, $property) {
return ($property->value??= [($this->init)()])[0];
}
public function set($self, $property, $value) {
$property->value= [$value];
}
}
class InitOnly {
public function get($self, $property) {
return $property->value[0];
}
public function set($self, $property, $value) {
if ($property->value) throw new IllegalStateException('Can not be modified after initialization');
$property->value= [$value];
}
}
class Observable {
public function __construct(private callable $observer) { }
public function get($self, $property) {
return $property->value;
}
public function set($self, $property, $value) {
if (false === ($this->observer)($property->value, $value)) return;
$property->value= $value;
}
}
class Configured {
public function __construct(private Properties $config, private ?string $section= null) { }
public function get($self, $property) {
if (null === $property->value) {
$setting= preg_replace_callback('/([a-z]+)([A-Z])/', fn($m) => $m[1].'-'.strtolower($m[2]), $property->name);
$property->value= match ($property->type) {
'bool' => $this->config->readBool($this->section, $setting),
'int' => $this->config->readInteger($this->section, $setting),
'float' => $this->config->readFloat($this->section, $setting),
'string' => $this->config->readString($this->section, $setting),
'array' => $this->config->readArray($this->section, $setting),
};
}
return $property->value;
}
}
class Delegate {
private static $INITONLY;
public static function initonly() { return self::$INITONLY??= new InitOnly(); }
} Lazy exampleclass Environment {
public string $user as new ByLazy(function() {
Console::writeLine('Getting environment variable');
return getenv('USER');
});
}
$env= new Environment();
isset($argv[1]) && $env->user= $argv[1];
Console::writeLine($env->user); // Gets env var unless initialized above
Console::writeLine($env->user); // Prints cached copy Init only exampleclass Person {
public string $name as Delegate::initonly();
public function __construct($name) { $this->name= $name; }
}
$person= new Person($argv[1]);
try {
$person->name= 'Modified';
} catch (IllegalStateException $e) {
Console::writeLine('Caught expected ', $e);
}
Console::writeLine($person->name); Observabe exampleclass Employee {
public Money $salary= new Money(0, Currency::$EUR) as new Observable(function($old, $new) {
if ($old->compareTo($new) < 0) {
Console::writeLine('Prevented salary cut from ', $old, ' -> ', $new, '!');
return false;
}
Console::writeLine($this->name, '\'s salary changing from ', $old, ' -> ', $new);
});
public function __construct(private $name) { }
}
$emp= new Employee('Test');
$emp->salary= new Money(100_000, Currency::$EUR); // Test's salary changing ...
$emp->salary= new Money(90_000, Currency::$EUR); // Prevented salary cut ...
Console::writeLine($emp->salary); // 100,000.00 EUR Configuration exampletitle=Test
os[]=Windows
os[]=MacOS
os[]=Un*x
new-window=true class Preferences {
public string $title as $this->configured;
public array $os as $this->configured;
public bool $newWindow as $this->configured;
public function __construct(private Configured $configured) { }
}
$pref= new Preferences(new Configured(new Properties('config.ini')));
Console::writeLine('Title "', $pref->title, '"'); // Title "Test"
Console::writeLine('OS ', $pref->os); // OS ["Windows", "MacOS", "Un*x"]
Console::writeLine('New window? ', $pref->newWindow); // New window? true |
It would be great to have a builtin For some added safety, delegates could be forced to implement a builtin |
As noted in the "Future Scope" section of the property hooks RFC, reusable package hooks are not part of the first RFC, but the authors @iluuu1994 and @Crell envision it being added later. Here are some ideas of how it could be implemented.
Swift
The concept here is called property wrappers. Here's an example from their documentation:
See https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties/#Property-Wrappers
Kotlin
The concept here is called property delegates. Here's an example of how these are implemented:
See https://blog.kotlin-academy.com/kotlin-programmer-dictionary-field-vs-property-30ab7ef70531
Idea for PHP
Going down the same path as Kotlin (which the PHP RFC is inspired quite a bit from) but without introducing any new keywords, we could come up with the following:
The
ByLazy
implementation is as follows:Note: Using an array for a value will allow initializing the property to NULL.
See also
The text was updated successfully, but these errors were encountered: