So the question remains unanswered - whether the initializer gets created only once, and it matters for performance I think, but I'm not sure.
Well, closure instance is created only once. But anyway, performance will depend not on closure instance creation time (since it is insignificant), but closure execution time.
I'd be happy to use & $this->x instead as a first parameter, but then inside lazy() function I don't have a key to use for $__lazilyLoaded array to keep track of what has been initialized and what has not. How could I solve this problem? Using hash as a key isn't safe, nor it can be generated for callbacks like array($object, 'methodName')
I can propose the following solution:
<?php
trait Lazy
{
private $_lazyProperties = [];
private function getPropertyValue($propertyName) {
if(isset($this->_lazyProperties[$propertyName])) {
return $this->_lazyProperties[$propertyName];
}
if(!isset($this->_propertyLoaders[$propertyName])) {
throw new Exception("Property $propertyName does not have loader!");
}
$propertyValue = $this->_propertyLoaders[$propertyName]();
$this->_lazyProperties[$propertyName] = $propertyValue;
return $propertyValue;
}
public function __call($methodName, $arguments) {
if(strpos($methodName, 'get') !== 0) {
throw new Exception("Method $methodName is not implemented!");
}
$propertyName = substr($methodName, 3);
if(isset($this->_lazyProperties[$propertyName])) {
return $this->_lazyProperties[$propertyName];
}
$propertyInializerName = 'lazy' . $propertyName;
$propertyValue = $this->$propertyInializerName();
$this->_lazyProperties[$propertyName] = $propertyValue;
return $propertyValue;
}
}
/**
* @method getX()
**/
class Test
{
use Lazy;
protected function lazyX() {
echo("Initalizer called.\r\n");
return "X THE METHOD";
}
}
echo "<pre>";
$t = new Test;
echo $t->getX() . "\n";
echo $t->getX() . "\n";
echo "</pre>";
Result:
c:\Temp>php test.php
<pre>X THE METHOD
X THE METHOD
</pre>
c:\Temp>php test.php
<pre>Initalizer called.
X THE METHOD
X THE METHOD
</pre>
c:\Temp>
You cannot always be protected from forgetting something, but it is easier to remember when all things are close to each other. So, I propose to implement lazy loaders as methods on corresponding classes with specific names. To provide autocomplete @method
annotation can be used. In a good IDE refactoring method name in annotation will allow to rename method across all project. Lazy loading function will be declared in the same class so renaming it also is not a problem.
By declaring a function with a name, starting with "lazy", in my example you both declare a corresponding accessor function, with name starting with "get" and it's lazy loader.
If $this->x is a private property, it's safe for outer world to call the x() method, but for the class' methods it's still unsafe to access the raw $this->x property as it can be still uninitialized. So I wonder is there a better way - maybe I should save all the values in some Trait's field?
Trait fields are available in the class, that uses specific trait. Even private fields. Remember, this is composition, not inheritance. I think it's better to create private trait array field and store your lazy properties there. No need to create a new field for every property.
But I cannot say I like the whole scheme. Can you explain the use of it for you? May be we can come with better solution.