126

可能重复:
魔术方法是 PHP 中的最佳实践吗?

这些是简单的示例,但假设您的类中有两个以上的属性。

什么是最佳实践?

a) 使用 __get 和 __set

class MyClass {
    private $firstField;
    private $secondField;

    public function __get($property) {
            if (property_exists($this, $property)) {
                return $this->$property;
            }
    }

    public function __set($property, $value) {
        if (property_exists($this, $property)) {
            $this->$property = $value;
        }
    }
}

$myClass = new MyClass();

$myClass->firstField = "This is a foo line";
$myClass->secondField = "This is a bar line";

echo $myClass->firstField;
echo $myClass->secondField;

/* Output:
    This is a foo line
    This is a bar line
 */

b) 使用传统的 setter 和 getter

class MyClass {

    private $firstField;
    private $secondField;

    public function getFirstField() {
        return $this->firstField;
    }

    public function setFirstField($firstField) {
        $this->firstField = $firstField;
    }

    public function getSecondField() {
        return $this->secondField;
    }

    public function setSecondField($secondField) {
        $this->secondField = $secondField;
    }

}

$myClass = new MyClass();

$myClass->setFirstField("This is a foo line");
$myClass->setSecondField("This is a bar line");

echo $myClass->getFirstField();
echo $myClass->getSecondField();

/* Output:
    This is a foo line
    This is a bar line
 */

在这篇文章中:http: //blog.webspecies.co.uk/2011-05-23/the-new-era-of-php-frameworks.html

作者声称使用魔术方法不是一个好主意:

首先,当时使用 PHP 的魔法函数(__get、__call 等)非常流行。乍一看,它们并没有什么问题,但实际上它们确实很危险。它们使 API 不清楚,无法自动完成,最重要的是它们很慢。他们的用例是破解 PHP 来做它不想做的事情。它奏效了。却让坏事发生。

但我想听听更多关于这方面的意见。

4

9 回答 9

168

过去我就是你的情况。我选择了魔法方法。

这是一个错误,你问题的最后一部分说明了一切:

  • 这比 getter/setter
  • 没有自动完成(这实际上是一个主要问题),并且由 IDE 进行类型管理以进行重构和代码浏览(在 Zend Studio/PhpStorm 下,这可以使用@propertyphpdoc 注释来处理,但需要维护它们:相当痛)
  • 文档(phpdoc)与您的代码应该如何使用不匹配,并且查看您的课程也不会带来太多答案。这令人困惑。
  • 编辑后添加:为属性设置 getter与“真实”方法更一致,其中getXXX()不仅返回私有属性,而且执行真实逻辑。你有相同的命名。例如,您有$user->getName()(返回私有财产)和$user->getToken($key)(计算)。当你的 getter 不仅仅是一个 getter 并且需要做一些逻辑的时候,一切仍然是一致的。

最后,这是 IMO 最大的问题:这很神奇。魔法是非常非常糟糕的,因为你必须知道魔法是如何运作的才能正确使用它。这是我在团队中遇到的一个问题:每个人都必须了解魔法,而不仅仅是你。

getter 和 setter 写起来很痛苦(我讨厌它们),但它们是值得的。

于 2011-05-31T08:30:57.733 回答
131

如果对象确实“神奇”,您只需要使用魔法。如果您有一个具有固定属性的经典对象,则使用 setter 和 getter,它们可以正常工作。

如果您的对象具有动态属性,例如它是数据库抽象层的一部分,并且它的参数是在运行时设置的,那么您确实需要魔术方法来方便。

于 2011-05-31T08:42:36.560 回答
91

__get尽可能多地使用(和公共属性),因为它们使代码更具可读性。相比:

这段代码明确地说明了我在做什么:

echo $user->name;

这段代码让我觉得很愚蠢,我不喜欢:

function getName() { return $this->_name; }
....

echo $user->getName();

当您一次访问多个属性时,两者之间的区别尤其明显。

echo "
    Dear $user->firstName $user->lastName!
    Your purchase:
        $product->name  $product->count x $product->price
"

echo "
    Dear " . $user->getFirstName() . " " . $user->getLastName() . "
    Your purchase: 
        " . $product->getName() . " " . $product->getCount() . "  x " . $product->getPrice() . " ";

是否$a->b应该真正做某事或只是返回一个值是被调用者的责任。对于调用者,$user->name应该$user->accountBalance看起来一样,尽管后者可能涉及复杂的计算。在我的数据类中,我使用以下小方法:

 function __get($p) { 
      $m = "get_$p";
      if(method_exists($this, $m)) return $this->$m();
      user_error("undefined property $p");
 }

当有人调用$obj->xxx并且类已经get_xxx定义时,该方法将被隐式调用。因此,您可以在需要时定义一个 getter,同时保持界面统一和透明。作为额外的奖励,这提供了一种优雅的方式来记忆计算:

  function get_accountBalance() {
      $result = <...complex stuff...>
      // since we cache the result in a public property, the getter will be called only once
      $this->accountBalance = $result;
  }

  ....


   echo $user->accountBalance; // calculate the value
   ....
   echo $user->accountBalance; // use the cached value

底线:php 是一种动态脚本语言,这样使用它,不要假装你在做 Java 或 C#。

于 2011-05-31T09:25:57.333 回答
2

我投票支持第三种解决方案。我在我的项目中使用它,Symfony 也使用这样的东西:

public function __call($val, $x) {
    if(substr($val, 0, 3) == 'get') {
        $varname = strtolower(substr($val, 3));
    }
    else {
        throw new Exception('Bad method.', 500);
    }
    if(property_exists('Yourclass', $varname)) {
        return $this->$varname;
    } else {
        throw new Exception('Property does not exist: '.$varname, 500);
    }
}

这样你就拥有了自动的 getter(你也可以编写 setter),并且只有在成员变量有特殊情况时才需要编写新方法。

于 2011-05-31T07:44:19.553 回答
2

我混合了 edem 的答案和你的第二个代码。这样,我就拥有了常见的 getter/setter(IDE 中的代码完成)的好处,如果我愿意,可以轻松编码,由于不存在的属性而导致的异常(非常适合发现拼写错误:$foo->naem而不是$foo->name),只读属性和复合属性。

class Foo
{
    private $_bar;
    private $_baz;

    public function getBar()
    {
        return $this->_bar;
    }

    public function setBar($value)
    {
        $this->_bar = $value;
    }

    public function getBaz()
    {
        return $this->_baz;
    }

    public function getBarBaz()
    {
        return $this->_bar . ' ' . $this->_baz;
    }

    public function __get($var)
    {
        $func = 'get'.$var;
        if (method_exists($this, $func))
        {
            return $this->$func();
        } else {
            throw new InexistentPropertyException("Inexistent property: $var");
        }
    }

    public function __set($var, $value)
    {
        $func = 'set'.$var;
        if (method_exists($this, $func))
        {
            $this->$func($value);
        } else {
            if (method_exists($this, 'get'.$var))
            {
                throw new ReadOnlyException("property $var is read-only");
            } else {
                throw new InexistentPropertyException("Inexistent property: $var");
            }
        }
    }
}
于 2011-05-31T08:09:10.490 回答
-2

第二个代码示例是执行此操作的更合适的方法,因为您正在完全控制提供给class. 在某些情况下__set__get有用,但在这种情况下没有用。

于 2011-05-31T07:38:10.727 回答
-3

如果你想要魔法成员,你应该使用stdClass,如果你写一个类 - 定义它包含的内容。

于 2011-05-31T07:38:50.193 回答
-3

由于内省或反思,最佳实践是使用传统的 getter 和 setter。PHP 中有一种方法(与 Java 完全一样)可以获取方法或所有方法的名称。这样的事情将在第一种情况下返回“__get”,在第二种情况下返回“getFirstField”、“getSecondField”(加上setter)。

更多信息:http: //php.net/manual/en/book.reflection.php

于 2011-05-31T07:43:24.170 回答
-4

我现在回到 setter 和 getter,但我也将 getter 和 setter 放在魔术方法 __get 和 __set 中。这样,当我这样做时,我有一个默认行为

$class->var;

这只会调用我在 __get 中设置的 getter。通常我会直接使用 getter,但仍有一些情况更简单。

于 2011-05-31T09:18:18.733 回答