我想在PHP中延迟加载类的公共数据成员。假设我们有以下类:
<?php
class Dummy
{
public $name;
public $age;
public $status_indicator;
}
?>
如果$name
,$age
和$status_indicator
是私有数据成员,我会通过他们的getter方法延迟加载它们,但由于它们是公开的 - 我不知道如何延迟加载他们。这可能吗?
修改的:
有人评论说,有一个名为__get
的方法可能有助于解决这个问题,但我不明白。
答案 0 :(得分:2)
您可以使用__get
来模拟在首次访问时真正动态加载的公共成员。当您尝试访问对象的未定义成员时,PHP将调用__get
并将其尝试访问的成员的名称传递给它。例如,如果类已定义$x->my_variable
方法,则访问__get("my_variable")
将调用__get_
。
在此示例中,$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
。
答案 1 :(得分:1)
这是我在最近的一个项目中使用的一些代码:
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;
}
}
这意味着您只需要使用魔法__get
和set
一次,然后简单地命名您的方法,正确的事情将使其表现为getter,setter和lazy初始值设定项。
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
答案 2 :(得分:0)
要扩展注释,您可以使用一个字段数组和一些神奇的方法魔法来处理加载操作:
<?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;
}
}
?>
答案 3 :(得分:0)
您可以简单地删除它们并使用魔术方法__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;
}
}
}
答案 4 :(得分:0)
使用__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;
}
答案 5 :(得分:0)
首先:这正是您应该使用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
}
}
答案 6 :(得分:0)
使用与OP相同的示例,您可以像保护/私有一样延迟加载公共成员。有几种解决方法就像OP提到的那样,使用getter方法来执行延迟加载逻辑。 __get()
不能与同一个类的公共成员一起使用,因为直接访问成员意味着永远不会调用__get()
。将它们隐藏在数组中会使__get
或getter
方法起作用,但这与将它们从公众可见性中删除基本相同,这显然是您试图避免的。
答案 7 :(得分:0)
正如我理解你的问题,你想知道你是否可以重载公共变量。
你已经知道了__get($ name)魔法,对吧?或许你在谈论 getName(), getAge()和* getStatus_Indicator()*。
在任何情况下,作为公共属性,你都无法使用神奇的方法。
我只是将它们列为概念证明。
<?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
*/
}
}
?>
示例3对结构的信息量最少,但就内存而言,这是最好的方法。我们在谈论延迟加载stuf ...即没有将无用的东西加载到内存中?
我这样说是因为:
class x { protected $var1 = array(); protected $var2 = 0.0; }
比
吃掉更多的记忆class x { protected $var1; protected $var2; }
甚至超过
class x { }
我通过构建数百万个对象并比较峰值内存使用情况来测试它。
答案 8 :(得分:0)
我认为拥有这样的“延迟加载属性”并不是一种好的编码风格。如果您需要一些延迟加载值,只需使用不带任何公共属性的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 ...