Reputation: 2698
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
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.
<?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());
}
}
?>
<?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 */
}
}
?>
<?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
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
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
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
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.
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
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
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
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
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