【问题标题】:When do/should I use __construct(), __get(), __set(), and __call() in PHP?什么时候/应该在 PHP 中使用 __construct()、__get()、__set() 和 __call()?
【发布时间】:2008-10-30 15:32:56
【问题描述】:

similar question discusses __construct,但我把它留在了我的标题中,供搜索找到这个的人使用。

显然,__get 和 __set 带有一个参数,即正在获取或设置的变量。但是,您必须知道变量名称(例如,知道该人的年龄是 $age 而不是 $myAge)。因此,如果您必须知道变量名,我不明白这一点,特别是如果您正在使用您不熟悉的代码(例如库)。

我找到了一些解释 __get()__set()__call() 的页面,但我仍然不明白它们为何或何时有用。

【问题讨论】:

    标签: php magic-function


    【解决方案1】:

    This page 可能会有用。 (请注意,您所说的不正确 - __set() 将变量名称和值都作为参数。__get() 仅采用变量名称。

    __get()__set() 在您希望提供对变量的通用访问的库函数中很有用。例如,在 ActiveRecord 类中,您可能希望人们能够将数据库字段作为对象属性进行访问。例如,在 Kohana PHP 框架中,您可能会使用:

    $user = ORM::factory('user', 1);
    $email = $user->email_address;
    

    这是通过使用__get()__set() 完成的。

    使用__call() 可以完成类似的事情,即您可以检测到有人何时调用 getProperty() 和 setProperty() 并进行相应处理。

    【讨论】:

      【解决方案2】:

      __get()、__set() 和 __call() 是 PHP 所说的“魔术方法”,我认为这有点愚蠢——我认为“挂钩”更贴切一些。反正我跑题了……

      这些的目的是在访问对象上定义的数据成员(属性或方法)时提供执行案例,可用于各种“聪明”的想法比如变量隐藏、消息转发等。

      但是,这是有代价的 - 调用这些的调用比调用定义的数据成员慢大约 10 倍。

      【讨论】:

      • 你有什么关于它变慢的参考吗?不是我不相信你(因为我相信),但我希望不是。
      【解决方案3】:

      重新定义__get__set 在核心类中特别有用。例如,如果您不希望您的配置被意外覆盖,但仍想从中获取数据:

      class Example
      {
          private $config = array('password' => 'pAsSwOrD');
          public function __get($name)
          {
              return $this->config[$name];
          }
      }
      

      【讨论】:

        【解决方案4】:

        另一个有用的魔术方法应用,尤其是__get__set__toString 是模板。您只需编写使用魔术方法的简单适配器,就可以使您的代码独立于模板引擎。如果您想迁移到另一个模板引擎,只需更改这些方法即可。

        class View {
        
            public $templateFile;
            protected $properties = array();
        
            public function __set($property, $value) {
                $this->properties[$property] = $value;
            }
        
            public function __get($property) {
                return @$this->properties[$property];
            }
        
            public function __toString() {
                require_once 'smarty/libs/Smarty.class.php';
                $smarty = new Smarty();
                $smarty->template_dir = 'view';
                $smarty->compile_dir = 'smarty/compile';
                $smarty->config_dir = 'smarty/config';
                $smarty->cache_dir = 'smarty/cache';
                foreach ($this->properties as $property => $value) {
                    $smarty->assign($property, $value);
                }
                return $smarty->fetch($this->templateFile);
            }
        
        }
        

        这种方法的隐藏好处是您可以将 View 对象嵌套在另一个对象中:

        $index = new View();
        $index->templateFile = 'index.tpl';
        
        $topNav = new View();
        $topNav->templateFile = 'topNav.tpl';
        
        $index->topNav = $topNav;
        

        index.tpl 中,嵌套看起来像这样:

        <html>
        <head></head>
        <body>
            {$topNav}
            Welcome to Foobar Corporation.
        </body>
        </html>
        

        只要您echo $index;,所有嵌套的 View 对象都会即时转换为字符串(准确地说是 HTML)

        【讨论】:

          【解决方案5】:

          我认为这不利于设计代码。如果你知道并做了一个好的设计,那么你就不需要在你的代码中使用__set()__get()。阅读您的代码也非常重要,如果您使用的是工作室(例如 Zend 工作室),使用 __set()__get() 您看不到您的类属性。

          【讨论】:

            【解决方案6】:

            PHP 允许我们动态创建可能导致问题的类变量。您可以使用 __set 和 __get 方法来限制这种行为。请参阅下面的示例...

            class Person { 
                  public $name;
                  public function printProperties(){
                   print_r(get_object_vars($this));
                  }
            }
            
            $person = new Person();
            $person->name = 'Jay'; //This is valid
            $person->printProperties();
            $person->age = '26';  //This shouldn't work...but it does 
            $person->printProperties();
            

            为了防止上述情况,你可以这样做..

            public function __set($name, $value){
                $classVar = get_object_vars($this);
                if(in_array($name, $classVar)){
                $this->$name = $value;
                }
            }
            

            希望这会有所帮助...

            【讨论】:

              【解决方案7】:

              他们是为了做“聪明”的事情。

              例如,您可以使用__set()__get() 与数据库通信。您的代码将是:$myObject-&gt;foo = "bar";,这可以在幕后更新数据库记录。当然,您必须对此非常小心,否则您的表现可能会受到影响,因此“聪明”周围的引号:)

              【讨论】:

                【解决方案8】:

                Overloading 方法在处理包含易于访问的数据的 PHP 对象时特别有用。访问不存在的属性时调用__get(),尝试写入不存在的属性时调用__set(),调用不存在的方法时调用__call()

                例如,假设有一个类管理您的配置:

                class Config
                {
                    protected $_data = array();
                
                    public function __set($key, $val)
                    {
                        $this->_data[$key] = $val;
                    }
                
                    public function __get($key)
                    {
                        return $this->_data[$key];
                    }
                
                    ...etc
                
                }
                

                这使得读取和写入对象变得更加容易,并让您在读取或写入对象时更改为使用自定义功能。 示例:

                $config = new Config();
                $config->foo = 'bar';
                
                echo $config->foo; // returns 'bar'
                

                【讨论】:

                • 我不喜欢这些方法的这种使用方式。它充其量只是语法糖,只是用来减慢您的应用程序的速度。我宁愿看到一个实现 SPL ArrayAccess 接口的类,或者只是一组通用的 get/set 方法。这些应该是“在必要时使用,而不是在方便时使用”。
                【解决方案9】:

                使用它们的一个很好的理由是注册系统(我认为 Zend 框架将其实现为注册或配置类 iirc),因此您可以执行类似的操作

                $conf = new Config();
                $conf->parent->child->grandchild = 'foo';
                

                每个属性都是一个自动生成的 Config 对象,类似于:

                function __get($key) {
                    return new Config($key);
                }
                

                显然如果 $conf->parent 已经存在,__get() 方法将不会被调用,因此使用它来生成新变量是一个不错的技巧。

                请记住,我刚刚引用的这段代码不是功能性的,我只是为了举例而快速编写它。

                【讨论】:

                  【解决方案10】:

                  可能不是世界上最简洁的设计,但我遇到过这样一种情况,即我有很多代码引用类中的实例变量,即:

                  $obj->value = 'blah';
                  echo $obj->value;
                  

                  但后来,我想在某些情况下设置“值”时做一些特别的事情,所以我重命名了值变量,并根据我需要的更改实现了 __set() 和 __get()。

                  其余的代码不知道有什么区别。

                  【讨论】:

                    猜你喜欢
                    • 2011-03-20
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-06-02
                    • 2023-03-20
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多