Question

First, take a look at this PHP 5.5.8 code which implements lazy initialization of class properties with using a Trait:

trait Lazy
{
private $__lazilyLoaded = [];

    protected function lazy($property, $initializer)
    {
        echo "Initializer in lazy() parameters has HASH = "
                . spl_object_hash($initializer) . "\n";

        if (!property_exists($this, $property)
                || !array_key_exists($property, $this->__lazilyLoaded))
        {
            echo "Initialization of property " . $property . "\n";
            $this->__lazilyLoaded[$property] = true;
            $this->$property = $initializer();
        }

        return $this->$property;
    }
}


class Test
{
    use Lazy;

    private $x = 'uninitialized';

    public function x()
    {
        return $this->lazy('x', function(){
            return 'abc';
        });
    }
}

echo "<pre>";

$t = new Test;
echo $t->x() . "\n";
echo $t->x() . "\n";

echo "</pre>";

The output is as follow:

uninitialized
Initializer in lazy() parameters has HASH = 000000001945aafc000000006251ed62
Initialization of property x
abc
Initializer in lazy() parameters has HASH = 000000001945aafc000000006251ed62
abc

Here are my questions and things I'd like to discuss and improve, but I don't know how.

  1. Based on the HASH values reported, it may appear that the initializer function is created only once. But actually uniqueness is not guaranteed between objects that did not reside in memory simultaneously. So the question remains unanswered - whether the initializer gets created only once, and it matters for performance I think, but I'm not sure.

  2. The way it's implemented now is not very safe in that if I refactor the code and change property $x to something else, I might forget to change the 'x' value as a first parameter to lazy() method. 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')

  3. 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?

The global aim is to make it:

a) Fast - acceptable enough for small and medium software applications

b) Concise in syntax - as much as possible, to be used widely in the methods of the classes which utilize the Lazy trait.

c) Modular - it would be nice if objects still held their own properties; I don't like the idea of one super-global storage of lazily-initialized values.

Thank you for your help, ideas and hints!

No correct solution

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top