【问题标题】:lazy load public class data member in PHPPHP中的延迟加载公共类数据成员
【发布时间】:2012-05-25 07:50:02
【问题描述】:

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

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

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

编辑: 有人评论说有一个方法叫__get 可能有助于解决这个问题,但我没看懂。

【问题讨论】:

  • 我认为可以通过延迟加载该类。
  • ..或通过__get魔术方法
  • 您可以隐藏数据(例如在数组中)并使用神奇的 __get() 和 __set() 方法提供对它们的访问。
  • 可以延迟加载对象,意思是在第一次调用的时候创建实例。创建实例时,会为非静态成员分配内存。我不确定“延迟加载成员”是什么意思
  • __get() 是当您尝试读取不可访问属性(私有或不存在)时“神奇地”调用的方法 - 它不适用于公共成员。我说对了吗? (看我的回答)

标签: php lazy-loading public-members


【解决方案1】:

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

在此示例中,$dummy-&gt;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

【讨论】:

  • 很好,其他成员如何为其他成员制作吸气剂,我是 php 新手
  • 是 $var =='_name' 还是没有下划线?
  • 是的。 $var 参数将包含被访问的变量的名称。
  • 很好,您将变量声明为 $_name 并在 if 语句中检查不带下划线 '_' 的名称
  • @user4o01 因为在第一种情况下,我正在检查一个包含"name"字符串。在第二种情况下,我正在访问一个名为 $_name 的私有变量。他们是不同的。如果你不明白这一点,你应该停止你正在做的事情并阅读 PHP 教程。这是非常基本的东西。
【解决方案2】:

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

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

【讨论】:

  • 如果你使用这篇文章上半部分的代码,你可以从EnhancedObject扩展你的类,然后如果你定义一个方法loadXYZ(),你可以做到myobj-&gt;xyz,它会运行该方法来延迟初始化它。
  • @user4o01 我已经更新了我的用法示例以适应您的情况
【解决方案3】:

要扩展 cmets,您可以使用字段数组和一点魔法方法来处理加载操作:

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

【讨论】:

    【解决方案4】:

    您可以简单地删除它们并使用魔术方法__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;
          }
       }
    }
    

    【讨论】:

    • 看起来很不错但是为什么return后的break语句?!!
    【解决方案5】:

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

    【讨论】:

    【解决方案6】:

    首先:这正是您应该使用 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
        }
    }
    

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

        我认为拥有这样的“延迟加载属性”并不是一种好的编码风格。如果您需要一些延迟加载值,只需使用不带任何公共属性的 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;
            }
        }
        

        有了这个类,你甚至可以为你的属性提供读/写机制,所以“年龄”可以读取但不能设置,“姓名”可以设置但不能读取:

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

        【讨论】:

          【解决方案9】:

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

          您已经知道 __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 { }
          

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

          【讨论】:

            猜你喜欢
            • 2011-09-20
            • 2017-12-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多