user4o01
user4o01

Reputation: 2698

lazy load public class data member in PHP

I want to lazy load the public data members of a class in PHP. Assume we have the following class:

<?php
class Dummy
{
    public $name;
    public $age;
    public $status_indicator;
}
?>

If $name, $age and $status_indicator were private data members I would lazy load them via their getter methods, but since they are public - I am unclear as to how to lazy load them. Is this possible?

EDIT: Someone commented that there is a method called __get which might help to solve this issue, but I didn't understand it.

Upvotes: 4

Views: 4720

Answers (9)

Mihai Stancu
Mihai Stancu

Reputation: 16107

As I understood your question, you wanted to know if you can overload public variables.

You already knew about the __get($name) magic method, right? Or maybe you were talking about getName(), getAge() and *getStatus_Indicator()*.

In any case as public properties you can't tap into the magic methods.

I'll just list them for proof of concept.

Example 1

<?php

class Dummy {
    public $name;
    public $age;
    public $status_indicator;

    public function __construct() {
        foreach($this AS $name => $value) {
            $this->$name = new LazyLoader($this, $name);
        }
    }
}



class LazyLoader {
    protected $object;
    protected $name;

    public function __construct($object, $name) {
        $this->object = $object;
        $this->name = $name;
    }
    public function load() {
        /* find the necessary $data somehow */
        $name = $this->name;
        $this->object->$name = $data;
        /*
           after the first call to LazyLoader::load
           the property no longer points to a LazyLoad
           object, but to the intended value
        */
    }
    public function __toString() {
        return($this->load());
    }
}

?>

Example 2

<?php

class LazyCollectionWrapper extends ArrayObject {
    public function __construct($objects = array()) {
        parent::__construct($objects);
    }

    public function offsetGet($offset) {
        $this->collection[$offset]->lazyLoad();
        return($this->collection[$offset]);
    }
}

class Dummy {
    public $name;
    public $age;
    public $status_indicator;

    public function lazyLoad() {
        /* load stuff here */
    }
}

?>

Example 3

<?php

class Dummy {
    public function __get() {
        /*
          load stuff here because undefined
          properties are caught by __get too
        */
    }
}

?>

Example 3 is least informative about the structure but as far as memory is concerned it's the best way to do it. We were talking about lazy loading stuf... i.e. not loading useless stuff into memory right?

I say this because:

class x { protected $var1 = array(); protected $var2 = 0.0; }

Eats up more memory than

class x { protected $var1; protected $var2; }

And even more than

class x { }

I've tested it by building millions of objects and comparing peak memory usages.

Upvotes: 0

rudi
rudi

Reputation: 791

I think it is not good coding style to have such "lazy-loading-properties". If you need some lazy loading values, simply use a get-method without any public property.

However I like this problem :) and I also like the most solutions, but I'd also like to add my generic solution here:

class Dummy
{
    private $_name;
    private $_age;
    private $_number_of_status;

    public function __get($name) {
        $var = "_".$name;
        $lazyLoadingMethod = "_load_".$name;
        if (!method_exists($this, $lazyLoadingMethod )) {
            trigger_error("Cannot access private property Dummy::$$name", E_USER_ERROR);
            return;
        }
        if (!isset($this->$var)) {
            $this->$var = $this->$lazyLoadingMethod();
        }
        return $this->$var;
    }

    public function __set($name, $value) {
        $var = "_".$name;
        $settingMethod = "_set_".$name;
        if (!method_exists($this, $settingMethod )) {
            trigger_error("Cannot access private property Dummy::$$name", E_USER_ERROR);
        } else {
            $this->$settingMethod($value);
        }
    }

    public function _load_age() {
        // lazy load here
        return 42;
    }

    public function _set_name($name) {
        echo "my new name is $name";
        $this->_name = $name;
    }
}

With this class you even have a read/write mechanism for your properties, so "age" can be read but not set, and "name" can be set but not read:

$d = new Dummy();
echo $d->age; //=> 42
$d->name = "Jon"; //my new name is Jon
echo $d->name; //=> Fatal error: Cannot access private property Dummy::$name in ...
$d->age = 100; //=> Fatal error: Cannot access private property Dummy::$age in ...

Upvotes: 0

smassey
smassey

Reputation: 5931

Using the same example as OP, you can't lazy load public members as you would protected/private. There are several work arounds which is like OP mentioned, using the getter methods to do the lazy loading logic. __get() would not work with public members of the same class because the direct access to the members means __get() would never be called. Hiding them in an array would make __get or getter methods work but thats basically the same as removing them from the public visibility which is obviously what you're trying to avoid.

Upvotes: 0

user229044
user229044

Reputation: 239301

You can use __get to simulate public members which are really dynamically loaded on first access. When you attempt to access an undefined member of an object, PHP will invoke __get and pass it the name of the member you attempted to access. For example, accessing $x->my_variable would invoke __get("my_variable") if the class had defined an __get_ method.

In this example, $dummy->name indirectly invokes the getter method getName, which initializes a private member named $_name on first access:

<?php
class Dummy
{
  private $_name;

  public function __get($var) {
    if ($var == 'name') {
      return $this->getName();
    } else if ($var == 'age') {
      // ...
    } else {
      throw "Undefined variable $var";
    }
  }

  public function getName() {
    if (is_null($this->_name)) {
      // Initialize and cache the value for $name
      $this->_name = expensive_function();
    }
    return $this->_name;
  }
}

$dummy = new Dummy();
echo $dummy->name;

You could similarly define and invoke other accessors like getAge.

Upvotes: 4

Eric
Eric

Reputation: 97591

Here's some code I used in a recent project:

class EnhancedObject {
    #Store cached properties here
    private $lazyProperties = array();

    #Allow $object->prop to alias to $object->getProperty().
    #Also, allow caching of $object->loadProp() to $object->prop
    public function __get($property) {
        $getter = "get".ucfirst($property);
        $loader = "load".ucfirst($property);

        if(method_exists($this, $getter)) {
            return $this->$getter();
        }
        elseif(method_exists($this, $loader)) {
            if(!isset($this->lazyProperties[$property])) {
                $this->lazyProperties[$property] = $this->$loader();
            }
            return $this->lazyProperties[$property];
        }
    }

    #Allow $object->prop = $value to alias to $object->setProperty($value).
    public function __set($property, $value) {
        $setter = "set".ucfirst($property);
        $loader = "load".ucfirst($property);

        if (method_exists($this, $setter)) {
            return $this->$setter($value);
        }
        elseif(method_exists($this, $loader)) {
            $this->lazyProperties[$property] = $value;
        }
        return $this;
    }
}

This means you only have to mess with the magic __get and set once, and then simply naming your methods the right things will make the behave as getters, setters, and lazy initializers.

Usage

class Dummy extends EnhancedObject {
    public $name;
    public $age;

    #Complex getters
    public function getSummary() {
        return "{$this->name}, aged {$this->age}";
    }

    #Cached getters for expensive operations
    public function loadStatusCount($id) {
        doExpensiveStuff();
        return 42;
    }
}

$d = new Dummy();
$d->name = "John Doe"
$d->age = 35

#getters
echo $d->summary; # echos "John Doe, aged 35"

#Lazy-initialized properties
echo $d->statusCount; # runs `doExpensiveStuff()`, echoing 42
echo $d->statusCount; # echos 42
echo $d->statusCount; # echos 42

Upvotes: 1

Niko
Niko

Reputation: 26730

First of all: This is exactly the reason why you should use getter/setter methods. That way, you can add additional logic to the process of retrieving the data.

Here is an example how to achieve public-member-like access using __get():

class Dummy {
    private $lazyMembers = array(
        'name' => 'NameOfTheClass'
    );

    public function __get($key) {
        if (isset($this->lazyMembers[$key])) {
            $obj = new $this->lazyMembers[$key]();
            $this->$key = $obj;
            return $obj;
        }

        // ..throw exception or whatever
    }
}

Upvotes: 0

Scott Saunders
Scott Saunders

Reputation: 30394

Use __get() http://us.php.net/manual/en/language.oop5.overloading.php

public function __get($name)
{
    if ($name == "age" and $this->age == null)) {
        // lazy load $this->age
        return $this->age;
    }

    return $this->$name;
} 

Upvotes: 0

Edson Medina
Edson Medina

Reputation: 10269

You could simply remove them and use magic method __get()

Something like:

class Dummy 
{ 
   public function __get($var_name) 
   {
      switch ($var_name) 
      {
        case 'name':
            // lazy load here
            return $val;
            break;

        case 'age':
            // lazy load here
            return $val;
            break;

        case 'number_of_status':
            // lazy load here
            return $val;
            break;
      }
   }
}

Upvotes: 0

rjz
rjz

Reputation: 16510

To expand on the comments, you could use an array of fields and a little magic method magic to handle the loading operation:

<?php
class Dummy
{
    protected $fields = array(
        'name' => null, 
        'age' => null,
        'etc' => null
    );

    public function __get ($key) {
        if (array_key_exists($key, $this->fields)) {
            if ($this->fields[$key] === null) {
                // do some kind of loading operation to set $value
                $this->fields[$key] = $value;
            }
            return $this->fields[$key];
        }
        return null;
    }
}
?>

Upvotes: 0

Related Questions