PHP 中的延迟加载公共类数据成员


lazy load public class data member in PHP

我想在PHP中延迟加载类的公共数据成员。假设我们有以下类:

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

如果$name$age$status_indicator是私有数据成员,我会通过他们的getter方法延迟加载它们,但由于它们是公共的 - 我不清楚如何延迟加载它们。这可能吗?

编辑:有人评论说,有一种叫做__get的方法可能有助于解决这个问题,但我不明白。

您可以使用

__get来模拟在首次访问时真正动态加载的公共成员。当您尝试访问对象的未定义成员时,PHP 将调用__get并向其传递您尝试访问的成员的名称。例如,如果类定义了__get_方法,则访问$x->my_variable将调用__get("my_variable")

在此示例中,$dummy->name间接调用 getter 方法getName ,该方法在首次访问时初始化名为 $_name 的私有成员:

<?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;

您可以类似地定义和调用其他访问器,例如 getAge

以下是我在最近的一个项目中使用的一些代码:

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;
    }
}

这意味着你只需要弄乱魔术__getset一次,然后简单地命名你的方法正确的东西就会使行为成为getter,setter和惰性的初始值设定项。

用法

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

要扩展注释,您可以使用一系列字段和一点魔术方法来处理加载操作:

<?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;
    }
}
?>

你可以简单地删除它们并使用魔术方法__get()

像这样:

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;
      }
   }
}

使用__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;
} 

首先:这正是你应该使用getter/setter方法的原因。这样,您就可以向检索数据的过程添加其他逻辑。

下面是如何使用 __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
    }
}

使用与 OP 相同的示例,您不能像受保护/私有那样延迟加载公共成员。有几种解决方法,就像 OP 提到的,使用 getter 方法来执行延迟加载逻辑。 __get()不会与同一类的公共成员一起工作,因为直接访问成员意味着永远不会调用__get()。将它们隐藏在数组中会使__getgetter方法起作用,但这基本上与将它们从公共可见性中删除相同,这显然是您要避免的。

我认为拥有这样的"延迟加载属性"不是好的编码风格。如果您需要一些延迟加载值,只需使用没有任何公共属性的 get 方法即可。

但是我喜欢这个问题:)我也喜欢大多数解决方案,但我也想在这里添加我的通用解决方案:

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;
    }
}

使用此类,您甚至可以为属性提供读/写机制,因此可以读取但不能设置"age",并且可以设置"name"但不能读取:

$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 ...

正如我理解你的问题,你想知道你是否可以重载公共变量。

你已经知道__get($name)魔法方法了,对吧?或者,也许您正在谈论getName()getAge()和*getStatus_Indicator()*。

无论如何,作为公共属性,您无法利用神奇的方法。

我将列出它们以进行概念验证。

例 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());
    }
}
?>

例 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 */
    }
}
?>

例 3

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

示例 3 对结构的信息最少,但就内存而言,这是最好的方法。我们谈论的是懒惰的装载装置...即不将无用的东西加载到内存中,对吗?

我这样说是因为:

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

消耗的内存比

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

甚至不止

class x { }

我通过构建数百万个对象并比较峰值内存使用量对其进行了测试。